Next chapter in the book is the longest one. But I will split it into two posts because it’s about two design patterns. The first one’s name is: iterator and it was mentioned at least once before. I recall it while describing examples of adapter pattern usage. The iterator pattern, unlike other design patterns, helps us encapsulate an iteration. Let’s start with the book example.
Book example
Many previous examples were connected to cooking and this time it’s similar. We have two places which are going to be merged. Objectville Dinner and Objectville Pancake House agreed to have the same implementation of MenuItem
:
# class MenuItem { private $name; private $description; private $vegetarian; private $price; public function __construct( $name, $description, $vegetarian, $price ) { $this->name = $name; $this->description = $description; $this->vegetarian = $vegetarian; $this->price = $price; } public function getName() { return $this->name; } public function getDescription() { return $this->description; } public function getPrice() { return $this->price; } public function isVegetarian() { return $this->vegetarian; } }
But they have two different implementations of their menus and no one is willing to change it. The Java examples from the book may reflect the issue better but I’ll try to describe it with PHP code, anyway. Originally, PancakeHouseMenu
uses ArrayList
structure whereas DinnerMenu
just a regular Array
. There is only array type in PHP and my examples look a little bit different than the Java onces:
# class DinnerMenu { private $menuItems; public function __construct() { $this->menuItems = new ArrayList(); $this->addItem( "K&B's Pancake Breakfast", "Pancakes with scrambled eggs, and toast", true, 2.99 ); $this->addItem( "Regular Pancake Breakfast", "Pancakes with fried eggs, sausage", false, 2.99 ); $this->addItem( "Blueberry Pancakes", "Pancakes made with fresh blueberries", true, 3.49 ); $this->addItem( "Waffles", "Waffles, with your choice of blueberries or strawberries", true, 3.59 ); } private function addItem( $name, $description, $vegetarian, $price ) { $menuItem = new MenuItem( $name, $description, $vegetarian, $price ); $this->menuItems->add( $menuItem ); } public function getMenuItems() { return $this->menuItems; } // other methods here... }
# class PancakeHouseMenu { const MAX_ITEMS = 6; private $numberOfItems = 0; private $menuItems; public function __construct() { $this->menuItems = array(); $this->addItem( "Vegetarian BLT", "(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99 ); $this->addItem( "BLT", "Bacon with lettuce & tomato on whole wheat", false, 2.99 ); $this->addItem( "Soup of the day", "Soup of the day with a side of potato salad", false, 3.29 ); $this->addItem( "Hotdog", "A hotdog, with saurkraut, relish, onions, topped with cheese", false, 3.05 ); } private function addItem( $name, $description, $vegetarian, $price ) { $menuItem = new MenuItem( $name, $description, $vegetarian, $price ); if( $this->numberOfItems >= static::MAX_ITEMS ) { echo "\nSorry, menu is full! Can't add item to menu."; } else { $this->menuItems[ $this->numberOfItems ] = $menuItem; $this->numberOfItems++; } } public function getMenuItems() { return $this->menuItems; } // other methods here... }
I implemented simple ArrayList
class in the PHP examples to make it easier to present the issue. And it exists in a client code which has to use the two menus — waitress One of the waitress’ functions would be printing the menus. To achieve this it’ll have to create instances of PancakeHouseMenu
and DinnerMenu
and call on them getMenuItems()
methods:
# $pancakeHouseMenu = new PancakeHouseMenu(); $dinnerMenu = new DinnerMenu(); $breakfastItems = $pancakeHouseMenu->getMenuItems(); $lunchItems = $dinnerMenu->getMenuItems();
The results will be of different types so it’ll require the waitress (client) to use two loops:
# for( $i = 0; $i < count( $breakfastItems ); $i++ ) { $item = $breakfastItems[$i]; echo "\n" . $item->getName() . " "; echo "\n" . $item->getPrice() . " "; echo "\n" . $item->getDescription() . "\n"; } for( $i = 0; $i < $lunchItems->size(); $i++ ) { $item = $lunchItems->get( $i ); echo "\n" . $item->getName() . " "; echo "\n" . $item->getPrice() . " "; echo "\n" . $item->getDescription() . "\n"; }
Imagine there are more restaurants merged and for each of them we’ve got different menu and to display it we need to add more loops… How to avoid it? Well, as always let’s see what’s changing element here. It’s the iteration through different items. And we can encapsulate it with iterator design pattern which diagram and definition are presented below.
The Iterator Pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
What we need to do is:
- Create iterators for our menus.
- Rework the menu with iterator.
- Fix up the waitress code.
Iterators for both menus are similar. The difference is in the implementation of next()
and current()
methods:
# class DinnerMenuIterator implements Iterator { // ... public function next() { $menuItem = $this->items->get( $this->position ); if( !is_null( $menuItem ) ) { $this->position++; return $menuItem; } else { return null; } } public function current() { return $this->items->get( $this->position ); } // ... }
# class PancakeHouseMenuIterator implements Iterator { // ... public function next() { if( isset( $this->items[ $this->position ] ) ) { $menuItem = $this->items[ $this->position ]; $this->position++; return $menuItem; } else { return null; } } public function current() { return $this->items[ $this->position ]; } // ... }
Changes in menu classes are simple: we add a getter which returns menu’s iterator and we remove getMenuItems()
methods because we don’t need it anymore and what’s more important: we don’t want to expose internal implementation of a menu.
Our waitress function of printing menu is handled now by two methods: first one creates menu iterators’ instances and passes them to the second method which in one loops prints items for both menus.
# class Waitress { // ... public function printMenu() { $breakfastIterator = $this->pancakeHouseMenu->createIterator(); $lunchIterator = $this->dinnerMenu->createIterator(); echo "\n=== MENU ===\n"; echo "\n=== BREAKFAST ===\n"; $this->doPrintMenu( $breakfastIterator ); echo "\n=== LUNCH ===\n"; $this->doPrintMenu( $lunchIterator ); } private function doPrintMenu( $iterator ) { while( ( $item = $iterator->next() ) instanceof MenuItem ) { echo "\n" . $item->getName() . " "; echo "\n" . $item->getPrice() . " "; echo "\n" . $item->getDescription() . "\n"; } } }
But the work done doesn’t reflect in 100% iterator pattern diagram. That’s because the client code depends on two classes where it could refer to each menu object using an interface rather than concrete class. Let’s create a Menu
interface with createIterator()
method. DinnerMenuIterator
and PancakeHouseMenuIterator
implement the new interface and reduce the dependency between Waitress
and the concrete class.
Later we’re informed about another place being merged with our Pancake House and Dinner. The above change makes it easy to take it into account. The new place’s menu just have to implement Menu
interface, make createIterator()
method available and we can use it in our Waitress
class. However, a small change there will make code easier to read and maintain. Let’s pass the menus’ objects to the Waitress
class as an array. Then in printMenu()
method just iterate through them and pass each’s iterator to doPrintMenu()
method:
# class Waitress { private $menus; public function __construct( $menus ) { $this->menus = $menus; } public function printMenu() { foreach( $this->menus as $menu ) { $menuIterator = $menu->createIterator(); $this->doPrintMenu( $menuIterator ); } } // ... }
Now, all the work is done and seems solid. We lost names of the menus but we could add the names to each menu. We also followed another design principle: single responsibility principle. It says that a class should have only one reason to change. If we would have left the classes responsibility of handling their aggregates and one more responsibility of iterating through aggregated elements then there would be two reasons to change. They would change if collections change and if the way we iterate through the collections changes. We know we want to avoid change in a class like the plague — modifying code provides all sorts of opportunities for problems to creep in. The above snippets are taken from full PHP example of iterator pattern usage I’ve written — feel free to take a look at it.
Other examples
Difference between types in Java and PHP was the hardest part to write this post’s code examples. Reading the part of the chapter about iterator design pattern was easy and with Java’s Array
, ArrayList
or Hashtable
types it was even easier to understand how iterators help. Those built-in objects have their method’s such as iterator()
which returns an iterator instances.
But PHP even though it doesn’t have so many collection types it has also quite cool built-in features. The list of built-in iterators is quite impressive. And unfortunately, I barely had chance to use them. Maybe it’s becauseof the feeling Stefan Froelich mentiones in his Sitepoint article about SPL Iterators. The overwhelming feeling when you see this impressive list of SPL iterators’ classes names.
However, once you start looking for examples of usage in Google you’ll find a lot of nice examples. You’ll find also quite markable words of one of the well known people from PHP world, founder of Sensio Labs:
(…)PHP also have a lot of awesome features; at least two of them are in my opinion largely underused: Iterators and Streams.Fabien Potencier
There is a simple example about using iterators and streams on his blog. Apropos Symfony there is a nice slide on Jack Smith’s presentation. The slide contains listing of iterators found in Symfony2. The presentation itself is really interesting because it shows several problems solved with iterators:
- building unsorted list from multi-dimensional array (a web page menu in example),
- removing recursively cache folders which are many levels deep in PHP,
- listing directories’ content without version control folders (
FilterIterator
), - pagination done with iterators (!) (
LimitIterator
),
At the end one more presentation. This one was prepared by Dr Tarique Sani with examples of code which we used old-school loops versus new-school iterators
Other design patterns
Here is the list of described by me design patterns: