The iterator pattern is used to access the elements of a collection object sequentially without exposing its underlying representation. It provides a way to traverse a collection without needing to know the internal details of how the collection is structured.

Applicability

  • When you want to provide a standard way to iterate through different types of collection (e.g. trees, lists, maps, etc.) without exposing their internal structure.
  • When you want to support multiple traversal algorithms over the same collection

Approach

  1. Define an iterator interface that declares methods like hasNext() and next() to access elements sequentially
  2. Create concrete implementations of the iterator that know how to travers a specific collection
  3. Define an aggregate (or iterable) interface that declares a method like createIterator() to return an iterator
  4. Implement the aggregate interface in the concrete collection class, which returns a suitable iterator interface
---
title: Iterator Design Pattern
---
classDiagram
    direction TB
    class Client
    Client --> Aggregate
    Client --> Iterator

    class Aggregate {
        <<interface>>
        +createIterator()
    }

    class CAggregate{
        +createIterator()
    }

    Aggregate<|.. CAggregate
    
    class Iterator {
        <<interface>>
        +hasNext()
        +next()
    }
    
    class CIterator{
        -addedState
        +hasNext()
        +next()
    }
    Iterator<|.. CIterator

    CAggregate <-- CIterator

    CAggregate ..> CIterator

Componentes

  • Iterator
    • An interface that defines how elements can be accessed and iterated
  • Concrete iterator
    • Implements the iterator interface, keeping track of the current position in the traversal
  • Agregate (iterable)
    • An interface that declares the method for creating an iterator
  • Concrete aggregate
    • Implements the aggregate interface and returns a new iterator instance that can traverse its elements

Example (Java)

Identify iterator pattern components

Aggregate: Container Concrete Aggregate: NameRepository Iterator: Iterator (abstract) Concrete Iterator: NameIterator

Aggregate (Iterable)

The Container Interface declares a method to return the iterator.

interface Container {
    Iterator getIterator();
}

Concrete aggregate

NameRepository implements the Container interface returns a NameIterator.

Support multiple iterator algorithms

A concrete aggregate can have many iterator algorithms, this is one of the strengths of the iterator pattern. It may require different traversal methods or ways to iterate over the same data structure. Such as forward or backward, in-order or pre-order (for tree structures), etc.

class NameRepository implements Container {
    private String[] names = { "Alice", "Bob", "Charlie", "Diana" };
    
	public enum IterationType {
    FORWARD, REVERSE, EVEN_INDEX
	}
	public Iterator getIterator(IterationType type) {
    switch (type) {
        case FORWARD: return new ForwardIterator(names);
        case REVERSE: return new ReverseIterator(names);
        case EVEN_INDEX: return new EvenIndexIterator(names);
        default: return new ForwardIterator(names);
    }
    
    private Iterator getForwardIterator() {
        return new ForwardIterator(names);
    }
    
    private Iterator getReverseIterator() {
        return new ReverseIterator(names);
    }
    
    private Iterator getEvenIndexIterator() {
        return new EvenIndexIterator(names);
    }
}

Iterator

This interface defines the standard operations for traversing a collection

interface Iterator {
	boolean hasNext();
	Object next();
}
 
 

Concrete iterator

The name repository supports different traversal orders and logics.

class ForwardIterator implements Iterator {
    private String[] names;
    private int index = 0;
    
    public ForwardIterator(String[] names) {
        this.names = names;
    }
    
    public boolean hasNext() {
        return index < names.length;
    }
    
    public Object next() {
        return hasNext() ? names[index++] : null;
    }
}
 
class ReverseIterator implements Iterator {
    private String[] names;
    private int index;
    
    public ReverseIterator(String[] names) {
        this.names = names;
        this.index = names.length - 1;
    }
    
    public boolean hasNext() {
        return index >= 0;
    }
    
    public Object next() {
        return hasNext() ? names[index--] : null;
    }
}
 
class EvenIndexIterator implements Iterator {
    private String[] names;
    private int index = 0;
    
    public EvenIndexIterator(String[] names) {
        this.names = names;
    }
    
    public boolean hasNext() {
        return index < names.length;
    }
    
    public Object next() {
        Object value = names[index];
        index += 2;
        return value;
    }
}

Application code

public class Demo {
    public static void main(String[] args) {
        NameRepository namesRepo = new NameRepository();
        
        System.out.println("=== Forward Iterator ===");
        Iterator forward = namesRepo.getForwardIterator();
        while (forward.hasNext()) {
            System.out.println("Name: " + forward.next());
        }
        
        System.out.println("\n=== Reverse Iterator ===");
        Iterator reverse = namesRepo.getReverseIterator();
        while (reverse.hasNext()) {
            System.out.println("Name: " + reverse.next());
        }
        
        System.out.println("\n=== Even Index Iterator ===");
        Iterator evenIndex = namesRepo.getEvenIndexIterator();
        while (evenIndex.hasNext()) {
            System.out.println("Name: " + evenIndex.next());
        }
    }
}

Example (Python)

The iterator pattern is built in to Python language. To make a class iterable, you need to implement the iterator protocol, which involves two dunder methods:

  • __iter__(self): Returns the iterator object
  • __next__(self): Returns the next value, or rises StopIteration when done
class NameCollection:
    def __init__(self, names):
        self.names = names
 
    def __iter__(self):
        return NameIterator(self.names)
 
class NameIterator:
    def __init__(self, names):
        self.names = names
        self.index = 0
 
    def __next__(self):
        if self.index >= len(self.names):
            raise StopIteration
        result = self.names[self.index]
        self.index += 1
        return result
 
    def __iter__(self):
        return self
 
collection = NameCollection(["Alice", "Bob", "Charlie"])
for name in collection:
    print(name)
Alice
Bob
Charlie

Advance singleton 1

We can access named objects, and these objects are guaranteed to be the same so long as you access the object using a specific unique name. This pattern is called a Multiton. Here is how you can define one.

class Multiton:
    _instances = {}
 
    @classmethod
    def get_instance(cls, key):
        if key not in cls._instances:
            # Create and store a new instance for the given key
            cls._instances[key] = cls(key)
        return cls._instances[key]
 
    def __init__(self, key):
        if key in self._instances:
            raise ValueError("Instance already exists for this key!")
        self.key = key
 
# Usage
instance1 = Multiton.get_instance("config1")
instance2 = Multiton.get_instance("config2")
same_instance = Multiton.get_instance("config1")
 
print(instance1 is same_instance)  # True
print(instance1 is instance2)  # False

Advanced singleton 2

Here is another example. You have several kinds of servers to serve different kinds of objects. They all provide somewhat the same interface. So, one way to manage such a list of objects is to use what is called a Registry pattern.

class Registry:
    def __init__(self):
        self._registry = {}
 
    def register(self, name, obj):
        if name in self._registry:
            raise ValueError(f"An object is already registered under the name '{name}'")
        self._registry[name] = obj
 
    def get(self, name):
        return self._registry.get(name, None)
 
    def unregister(self, name):
        if name in self._registry:
            del self._registry[name]
 
# Example usage
registry = Registry()
 
class DocServer:
    def __init__(self, args): ...
class ImgServer:
    def __init__(self, args): ...
 
# Registering objects
registry.register("documentserver", DocServer({"host": "localhost", "port": 5432}))
registry.register("imageserver", ImgServer({"host": "imgur", "port": 80}))
 
# Retrieving objects
doc_server = registry.get("documentserver")
imgserver = registry.get("imageserver")

Notice how the registry pattern is very reminiscent of the multiton pattern? The main difference between registry and multiton is that multiton also handles the creation of registered objects, while registry does not include that responsibility.


Back to super node: Behavioural Patterns

Design_Pattern Behavioural_Design_Patterns SOFT2201 Iterator_Pattern