TailTemplate Build stunning websites faster with our pre-designed Tailwind CSS templates

Understanding Design Patterns - Iterator

Provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

As a store manager, Eric’s job includes checking in products from each department. There are actually two departments in the store. One is called men's department, which holds products for men, and the other one is called women's department for women's products. Each department stores their products using different storage structures (data structures).

The Men's department stores products using array structure:

class MenDepartment
{
    private $_products = array();
    public function  getProducts()
    {
        return $this->_products;
    }
 
    public function addProduct(Product $product)
    {
        $this->_products[] = $product;
    }
}

The women's department stores products using SqlStack structure:

class WomenDepartment
{
    private $_products;
 
    public function __construct()
    {
        $this->_products = new SplStack();
    }
 
    public function  getProducts()
    {
        return $this->_products;
    }
 
    public function addProduct(Product $product)
    {
        $this->_products->push($product);
    }
}

So for Eric, the store manager, the code would look like this. Pay attention to function checkIn():

class StoreManager
{
    private $_menDepartment;
    private $_womenDepartment;
    public function __construct(MenDepartment $menDepartment, WomenDepartm
ent $womenDepartment)
    {
        $this->_menDepartment = $menDepartment;
        $this->_womenDepartment = $womenDepartment;
    }
 
    public function checkIn()
    {
       $menProducts = $this->_menDepartment->getProducts();
       foreach($menProducts as $menProduct) {
           echo $menProduct->getName();
       }
       $womenProducts = $this->_womenDepartment->getProducts();
       while (!$womenProducts->isEmpty()) {
           $womanProduct = $womenProducts->pop();
           echo $womanProduct->getName();
       }
    }
}

Depending on the data structure the department uses, we need to use different way to iterator the storage. This will quickly introduce a problem. Imagine if the women's department decides to use an array to store products too. We will not only need to update the WomenDepartment class, but we will also need to alter checkIn() method. This will apparently violate the single responsibility principle (SRP). This is because a class should have only one reason to change. Here the checkIn() method depends heavily on data structure that the two departments use. It has at least two reasons to change.

What if we can hide the underlying data structure that a department uses to store product and provide a universal way to iterator products? This is when we will need the Iterator Pattern.

Let us rework our code base.

First, we will need to create an interface called Iterator:

interface ProductIterator
{
   public function hasNext();
   public function next();
}

The Iterator interface defines two methods that the StoreManager will use to iterate the products.

Let us create a concrete iterator for MenDepartment class:

class MenDepartmentIterator implements ProductIterator
{
   private $_position = 0;
   private $_products = array();
   public function __construct($products)
   {
        $this->_products = $products;
   }
 
   public function hasNext()
   {
        return ($this->_position < count($this->_products));
   }
   public function next()
   {
        $product = $this->_products[$this->_position];
        $this->_position ++;
        return $product;
   }
}

We need an internal pointer in MenDepartment to keep track of the current index.

Let us create a concrete iterator for WomenDepartment class:

class WomenDepartmentIterator implements ProductIterator
{
   private $_products ;
   public function __construct($products)
   {
        $this->_products = $products;
   }
 
   public function hasNext()
   {
        return !($this->_products->isEmpty());
   }
 
   public function next()
   {
        $product = $this->_products->pop();
        return $product;
   }
}

Now we need to create a new method (createIterator) for MenDepartment and WomenDepartment. What this method does is to instantiate a concrete iterator designed previously. So that we can get their iterator directly:

class MenDepartment
{
    private $_products = array();
    public function  getProducts()
    {
        return $this->_products;
    }
 
    public function addProduct(Product $product)
    {
        $this->_products[] = $product;
    }
 
    public function createIterator()
    {
        return new MenDepartmentIterator($this->_products);
    }
}
 
class WomenDepartment
{
    private $_products;
    public function __construct()
    {
        $this->_products = new SplStack();
    }
 
    public function  getProducts()
    {
        return $this->_products;
    }
 
    public function addProduct(Product $product)
    {
        $this->_products->push($product);
    }
 
    public function createIterator()
    {
        return new WomenDepartmentIterator($this->_products);
    }
}

Lastly, let us see how StoreManager has changed. Rest all remain the same, the only place we need to change is checkIn() method, and we also need to add a facilitate method checkInByIterator() to make the code cleaner:

class StoreManager
{
    private $_menDepartment;
    private $_womenDepartment;
 
    public function __construct(MenDepartment $menDepartment, WomenDepartment $womenDepartment)
    {
        $this->_menDepartment = $menDepartment;
        $this->_womenDepartment = $womenDepartment;
    }
 
    public function checkIn()
    {
        $menDepartmentIterator   = $this->_menDepartment->createIterator();
        $womenDepartmentIterator = $this->_womenDepartment->createIterator();
        $this->checkInByIterator($menDepartmentIterator);
        $this->checkInByIterator($womenDepartmentIterator);
    }
 
    public function checkInByIterator(ProductIterator $productIterator)
    {
        while ($productIterator->hasNext()) {
            $product = $productIterator->next();
            echo $product->getName();
        }
    }
}

As we can see now, StoreManager is anti-fragile. It does not concern the data structures a department uses to store products. The code is now easier to maintain.

In our example, the Iterator Pattern provides a way to access the elements (products) of an aggregate object (MenDepartment and WomenDepartment objects) sequentially without exposing its underlying representations (Array or SqlStack).

The end

Hopefully this simple tutorial helped you with your development. If you like our post, please follow us on Twitter and help spread the word. We need your support to continue. If you have questions or find our mistakes in above tutorial, do leave a comment below to let us know.