Iterator jest operacyjnym wzorcem projektowym, który zapewnia sekwencyjny dostęp do elementów zbioru bez ujawniania jego reprezentacji. W PHP istnieje mechanizm iteratorów, jednakże, żeby lepiej zrozumieć na czym on polega postanowiłem zaimplementować Iterator.
Na poniższym obrazku znajduje się struktura wzorca:

Wzorzec Iterator w PHP
Na podstawie wzorca utworzyłem interfejsy i klasy potrzebne do jego implementacji oraz przykładową klasę Book, która będzie reprezentowała książkę.
Interfejs Iteratora
interface IIterator { function next(); function isDone(); function currentItem(); function hasNext(); }
Metoda next zwróci następny element w zbiorze.
Metoda isDone sprawdzi czy Iterator zakończył swoje działanie
Metoda currentItem zwróci aktualny element iteratora
Metoda hasNext sprawdzi czy istnieje następny element w Iteratorze.
Interfejs IAggregate
interface IAggregate { function createIterator(); }
Posiada metodę służącą do stworzenia i zwrócenia iteratora.
Aby ułatwić sobie pisanie i nie powtarzać go stworzyłem klasę abstrakcyjną Collection, która zawiera implementacje przydatnych metod w zbiorze, takich jak dodaj element, usuń element itp.
abstract class Collection { protected $items; protected function __construct() { $this->items = array(); } public function addItem($item) { array_push($this->items, $item); } public function removeItem($position) { if(isset($this->items[$position])) unset($this->items[$position]); } }
Dość już tej abstrakcji, tym razem stworzyłem klasę Book reprezentującą ksiązkę
class Book { private $name; function __construct($name) { $this->name = $name; } public function getName() { return $this->name; } }
Klasa w konstruktorze ustawia tytuł książki oraz posiada metodę getName(), która ten tytuł zwróci.
Następnie stworzyłem BookIterator czyli klasę reprezentującą Iterator dla zbioru książek. W lepszej wersji metody wykorzystane w tej klasie powinny być dziedziczone z abstrakcyjnej klasy Iterator, by mogły zostać wykorzystane w innych iteratorach, ale nie chciałem już bardziej mieszać w kodzie.
class BookIterator implements IIterator { private $position; private $items; function __construct($items) { $this->items = $items; $this->position = -1; } public function next() { return $this->items[++$this->position]; } public function hasNext() { return isset($this->items[$this->position + 1]); } public function isDone() { return count($this->items) == $this->position - 1; } public function currentItem() { $this->items[$this->position]; } }
Klasa BookIterator implementuje interfejs IIterator i definiuje metody, które ten interfejs zawiera. Są to proste operacje na zbiorach, tablicach, które jeśli to czytasz i dotarłeś do tego momentu, raczej nie trzeba Ci tłumaczyć.
Ostatnią klasą potrzebną do działania kodu będzie klasa BooksCollection, reprezentująca zbiór książek.
class BookCollection extends Collection implements IAggregate { function __construct() { parent::__construct(); } public function createIterator() { return new BookIterator($this->items); } }
Klasa ta dziedziczy z klasy Collection oraz implementuje interfejs IAggregate. W klasie tej wywoływany jest chroniony konstruktor z klasy Collection oraz zadeklarowana jest metoda, która służy do stworzenia Iteratora.
Mając to wszystko sprawdźmy jak to działa:
//utworzenie 3 przykładowych ksiązek $bookA = new Book('Lord of rings'); $bookB = new Book('50 shades of gray'); $bookC = new Book('Hunger games'); //utworznie zbioru ksiazek i dodanie wczesniej utworzonych obiektów do zbioru $books = new BookCollection(); $books->addItem($bookA); $books->addItem($bookB); $books->addItem($bookC); //utworzenie ksiązkowego iteratora $iterator = $books->createIterator(); //jesli iterator posiada nastepny element to go pobierz i wyświetl while($iterator->hasNext()) { $book = $iterator->next(); echo $book->getName()."\n"; }
Efektem działania kodu będzie wypisanie tytułów 3 książek w zbiorze.
Kiedy używać Iteratora?
- Jeśli chcesz udostępnić jednolity interfejs do poruszania się po zbiorach, kolekcjach w swoim programie, aplikacji.
- Jeśli chcesz uzyskać dostęp do zagregowanego obiektu i jego elementów bez ujawnienia jego reprezentacji
Iterator w PHP
W PHP istnieje zdefiniowany interfejs do iteratora i agregatora. Dzięki nim możesz używać iteratora np w pętli foreach.
Więcej na:
http://pl1.php.net/manual/en/class.iterator.php
http://pl1.php.net/manual/en/class.iteratoraggregate.php
http://en.wikipedia.org/wiki/Iterator_pattern
Bardzo konkretnie przedstawiony wzorzec. Pewnie jeszcze tutaj wrócę… 🙂 Mam tylko jedno pytanie. Konkretnie o implementację konkretnego iteratora (BookIterator). Wydaje mi się, że są w nim dwa błędy. Pierwszy w konstruktorze – inicjalizacja własności position wartością „-1” – wydaje mi się, że kod się wyłoży przy wywołaniu currentItem. Moim zdaniem w konstruktorze powinno być przypisane 0. Drugi problem dotyczy metody isDone – ona chyba nigdy nie zwróci true? Wydaje mi się że powinno być „+1” zamiast „-1”.
Dzięki za komentarz. Tak masz racje kod wyłoży się ponieważ jeśli stworzymy pusty iterator curr item będzie wskazywał na tablice $this->items[-1] co spowoduje błąd. Co do isDone() to wez sobie przykład:
mamy 3 elementy
count($this->items) = 3
kolejne wywołania next()
$position = 0, 1, 2
przy trzecim wywolaniu będziesz miał warunek 2 == 3 – 2 co jest równe true
Ten kod nie jest bez błedny bo nie chciałem zaśmiecać samej idei wzorca przez funkcje sprawdzające. Ten pierwszy błąd oczywiście poprawię dzięki za informacje.