-
Iterator pattern este probabil cel mai popular pattern din SPL. Este un exemplu foarte simplu de a demonstra avantajele unei interfete si SPL.
Motivatie:
Posibilitatea de a itera structuri de tip obiect folosind functii precum foreach(), var_dump(), print_r() etc.Diagrama:
Structura Iterator:
In SPL se gasesc mai multe interfete si clase pentru iterator.
Structura de baza pentru interfata Iterator:
1/** 2 * Interfata Iterator din SPL 3 */ 4Iterator extends Traversable { 5 /** 6 * Intoarce elementul curent 7 */ 8 abstract public mixed current ( void ) 9 10 /** 11 * Cheia pentru elementul curent 12 */ 13 abstract public scalar key ( void ) 14 15 /** 16 * Trece la urmatorul element 17 */ 18 abstract public void next ( void ) 19 20 /** 21 * Reseteaza iterarea la pozitia initiala 22 */ 23 abstract public void rewind ( void ) 24 25 /** 26 * Verifica daca pozitia curenta este valida 27 */ 28 abstract public boolean valid ( void ) 29}
Exemplu 1:
Un obiect iterabil simplu.
1/** 2 * Clasa care va genera un obiect interabil 3 */ 4class Iterabil implements Iterator { 5 6 /** 7 * Index pentru elementul iterabil 8 */ 9 private $_current = 0; 10 11 /** 12 * Array cu elementele de iterat 13 */ 14 private $_elements = array(); 15 16 /** 17 * Constructor 18 * 19 * @param array $elements Elementele de iterat 20 */ 21 public function __construct($elements) { 22 $this->_elements = $elements; 23 } 24 25 /** 26 * Elementul curent 27 * 28 * @return mixed Elementul curent 29 */ 30 public function current() { 31 return $this->_elements[$this->_current]; 32 } 33 34 /** 35 * Index curent 36 * 37 * @return integer Index curent 38 */ 39 public function key() { 40 return $this->_current; 41 } 42 43 /** 44 * Trecerea la index-ul urmator 45 */ 46 public function next() { 47 $this->_current++; 48 } 49 50 /** 51 * Resetare index 52 */ 53 public function rewind() { 54 $this->_current = 0; 55 } 56 57 /** 58 * Verifica daca elementul curent este setat 59 * 60 * @return boolean Daca elementu curent este setat 61 */ 62 public function valid() { 63 return isset($this->_elements[$this->_current]); 64 } 65} 66 67// instantiere clasa 68$obj = new Iterabil(array(1, 2, 3, 4, 5)); 69 70// iterare obiect 71foreach ($obj as $value) { 72 echo $value.PHP_EOL; 73} 74 75// output: 76// 1 77// 2 78// 3 79// 4 80// 5
Exemplu 2:
Un exemplu putin mai complex, o clasa care permite iterarea prin proprietatile publice ale clasei care o extinde. Se folosesc Iterator si Reflection.
1/** 2 * Clasa care va genera un obiect cu proprietatile publice iterabile 3 */ 4class Iterabil implements Iterator { 5 6 /** 7 * Index pentru elementul iterabil 8 */ 9 private $_current = 0; 10 11 /** 12 * Array cu elementele de iterat 13 */ 14 private $_elements = array(); 15 16 /** 17 * Elementul curent 18 * 19 * @return mixed Proprietatea curenta in iteratie 20 */ 21 public function current() { 22 return $this->_elements[$this->_current]->name; 23 } 24 25 /** 26 * Index curent 27 * 28 * @return integer Index curent 29 */ 30 public function key() { 31 return $this->_current; 32 } 33 34 /** 35 * Trecerea la index-ul urmator 36 */ 37 public function next() { 38 $this->_current++; 39 } 40 41 /** 42 * Resetare index si preluare prorietari 43 */ 44 public function rewind() { 45 // rewind se apeleaza prima, 46 // deci aici ar trebui preluate prorprietatile obiectului 47 // se initializeaza obiectul de tip ReflectionClass 48 // cu parametru numele clasei curente 49 $reflection = new ReflectionClass(get_class($this)); 50 51 // se extrag metodele publice 52 $this->_elements = $reflection->getProperties(ReflectionMethod::IS_PUBLIC); 53 54 // se seteaza indexul curent 55 $this->_current = 0; 56 } 57 58 /** 59 * Verifica daca elementul curent este setat 60 * 61 * @return boolean Daca elementu curent este setat 62 */ 63 public function valid() { 64 return isset($this->_elements[$this->_current]); 65 } 66} 67 68/** 69 * O noua clasa care va avea proprietati publice 70 * 71 */ 72class Testing extends Iterabil { 73 public $proprietate1; 74 public $proprietate2; 75} 76 77// instantiere clasa 78$obj = new Testing(); 79 80// iterare obiect 81foreach ($obj as $value) { 82 echo $value.PHP_EOL; 83} 84 85// output: 86// proprietate1 87// proprietate2
Iar daca vrem ca exemplu de mai sus sa fie si accesibil ca un array, nu trebuie decat sa mai implementam din SPL ArrayAccess.
Structura ArrayAccess:
1ArrayAccess { 2 /** 3 * Verifica daca un offset exista 4 */ 5 abstract public boolean offsetExists ( string $offset ); 6 7 /** 8 * Intoarce elementul unui offset sau NULL daca nu exista 9 */ 10 abstract public mixed offsetGet ( string $offset ); 11 12 /** 13 * Seteaza o valoare pentru un offset 14 */ 15 abstract public void offsetSet ( string $offset , string $value ); 16 17 /** 18 * Dezaloca o valoare pentru un offset 19 */ 20 abstract public void offsetUnset ( string $offset ) 21}
Exemplu 3:
Un exemplu destul de dificil care are rolul de a evidentia puterea interfetelor din SPL. Obiect iterabil si accesibil ca un array.
Pentru a simplifica logica de acces am folosit chiar functiile pentru parcurgerea unui array (next(), reset()).
1/** 2 * Clasa care va genera un obiect interabil 3 */ 4class Iterabil implements Iterator, ArrayAccess, Countable { 5 6 /** 7 * Array cu elementele de iterat 8 */ 9 private $_elements = array(); 10 11 /** 12 * Constructor 13 * 14 * @param array $elements Elementele de iterat 15 */ 16 public function __construct($elements) { 17 $this->_elements = $elements; 18 } 19 20 /** 21 * Elementul curent 22 * 23 * @return mixed Elementul curent 24 */ 25 public function current() { 26 return current($this->_elements); 27 } 28 29 /** 30 * Index curent 31 * 32 * @return integer Index curent 33 */ 34 public function key() { 35 return key($this->_elements); 36 } 37 38 /** 39 * Trecerea la index-ul urmator 40 */ 41 public function next() { 42 next($this->_elements); 43 } 44 45 /** 46 * Resetare index 47 */ 48 public function rewind() { 49 reset($this->_elements); 50 } 51 52 /** 53 * Verifica daca elementul curent este setat 54 * 55 * @return boolean Daca elementu curent este setat 56 */ 57 public function valid() { 58 return current($this->_elements)?true:false; 59 } 60 /** 61 * Verifica daca un offset exista 62 * 63 * @param string $offset Element cautat 64 * @return boolean Daca elementul de la offset exista 65 */ 66 public function offsetExists($offset) { 67 return isset($this->_elements[$offset]); 68 } 69 70 /** 71 * Elementul de la un offset 72 * 73 * @param string $offset Offset array 74 * @return mixed 75 */ 76 public function offsetGet($offset) { 77 return $this->_elements[$offset]; 78 } 79 80 /** 81 * Setare element in array 82 * 83 * @param string $offset Offset element 84 * @param mixed $value Valoarea pentru elementul din array 85 */ 86 public function offsetSet($offset, $value) { 87 $this->_elements[$offset] = $value; 88 } 89 90 /** 91 * Dezalocare element in array 92 * 93 * @param string $offset Offset element 94 */ 95 public function offsetUnset($offset) { 96 unset($this->_elements[$offset]); 97 } 98 99 /** 100 * Numarul de elemente din array-ul curent 101 * 102 * @return integer Nr elemente array 103 */ 104 public function count() { 105 return count($this->_elements); 106 } 107} 108 109// instantiere clasa 110$obj = new Iterabil(array(1, 2, 3, 4, 5)); 111 112echo 'Iterare folosind "for":'.PHP_EOL; 113 114// iterare ca printr-un array simplu 115for($i = 0; $i < count($obj); $i++) { 116 echo $obj[$i].PHP_EOL; 117} 118 119echo 'Element de sters: '.$obj[1].PHP_EOL; 120 121unset($obj[1]); 122 123echo 'Iterare folosind "foreach":'.PHP_EOL; 124 125// iterare array folosind foreach 126foreach ($obj as $element) { 127 echo $element.PHP_EOL; 128} 129 130// Output: 131//Iterare folosind "for": 132//1 133//2 134//3 135//4 136//5 137//Element de sters: 2 138//Iterare folosind "foreach": 139//1 140//3 141//4 142//5
-
Observer pattern se refera la un obiect “subiect” care are asociata o lista de obiecte dependente, numite observatori, pe care le apeleaza automat de fiecare data cand se intampla o actiune.
Un mic exemplu de ce se foloseste:
– sa zicem ca avem o clasa pe care se fac niste modificari:
1class Actiune { 2 private $val; 3 function __construrct() { 4 // ceva cod in constructor 5 } 6 7 function change($val) { 8 $this->val = $val; 9 } 10}
De fiecare data cand se face modifica $val vrem sa se apeleze o metoda a unui obiect “observator”:
1class Actiune { 2 private $val; 3 function __construrct() { 4 // ceva cod in constructor 5 } 6 7 function change($val, $observator) { 8 $this->val = $val; 9 $observator->update($this); 10 } 11}
Teoretic nu suna rau, dar cu cat sunt mai multe metode cu atat exista o dependenta mai mare si de fiecare data cand se adauga un obiect nou de tip observator trebuie modificata clasa, avand toate sansele sa rezulte intr-un haos aproape imposibil de portat.
Acum observator pattern arata cam asa:
SPL (Standard PHP Library), care este bine cunoscut pentru iteratorii definiti, vine cu interfetele SplSubject si SplObserver, pentru subiect respectiv observator.
O implementare arata cam asta:
1/** 2 * clasa care trebuie urmarita 3 */ 4class Actiune implements SplSubject { 5 private $observatori = array(); 6 private $val; 7 8 /** 9 * metoda atasare obiect observator 10 * 11 * @param SplObserver $observator 12 */ 13 function attach(SplObserver $observator) { 14 $this->observatori[] = $observator; 15 } 16 17 /** 18 * metoda deatasare obiect observator 19 * 20 * @param SplObserver $observator 21 */ 22 function detach(SplObserver $observator) { 23 $observatori = array(); 24 foreach($this->observatori as $observatorul) { 25 if($observatorul != $observator) $observatori[] = $observatorul; 26 } 27 $this->observatori = $observatori; 28 } 29 30 /** 31 * metoda care notifica obiectele de tip observator 32 */ 33 function notify() { 34 foreach($this->observatori as $observator) { 35 $observator->update($this); 36 } 37 } 38 39 /** 40 * metoda care face modificarea in clasa 41 * 42 * @param int $val 43 */ 44 function update($val) { 45 echo 'facem update... 46'; 47 $this->val = $val; 48 $this->notify(); 49 } 50 51 /** 52 * metoda publica care intoarce statusul obiectului 53 * 54 * @return int 55 */ 56 function getStatus() { 57 return $this->val; 58 } 59} 60 61/** 62 * o clasa observator 63 */ 64class Observator implements SplObserver { 65 function update(SplSubject $subiect) { 66 echo $subiect->getStatus(); 67 } 68} 69 70// instanta observator 71$observator = new Observator(); 72 73// instanta subiect 74$subiect = new Actiune(); 75 76// atasare observator la subiect 77$subiect->attach($observator); 78 79// update subiect 80$subiect->update(5);
Ce mi se pare mie ciudat este ca nu exista o documentatie pentru aceste interfetele din SPL. Chiar pe site-ul zend exista un articol PHP Patterns: The Observer Pattern care nu foloseste SPL, iar asta in masura in care exista documentatie pentru namespaces chiar inainte sa apara PHP 5.3.