Iterator Design Pattern (Behavioral)
Iterator is a behavioral design pattern that lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.).
Problem
Section titled βProblemβCollections are one of the most common data structures in programming. However, a collection is just a container for a group of objects. Most collections store their elements in simple lists, but some are based on stacks, trees, graphs, and other complex structures.
Regardless of how a collection is structured, it must provide some way of accessing its elements so that other code can use these elements. There should be a way to go through each element of the collection without accessing the same elements over and over.
This may seem like an easy job if you have a collection based on a list. You just loop over all of the elements. But how do you sequentially traverse elements of a complex data structure, such as a tree? For example, one day you might be just fine with depth-first traversal of a tree. Yet the next day you might require breadth-first traversal. Adding more and more traversal algorithms to the collection gradually blurs its primary responsibility, which is efficient data storage.
Solution
Section titled βSolutionβThe main idea of the Iterator pattern is to extract the traversal behavior of a collection into a separate object called an iterator.
In addition to implementing the algorithm itself, an iterator object encapsulates all of the traversal details, such as the current position and how many elements are left till the end. Because of this, several iterators can go through the same collection at the same time, independently of each other.
All iterators must implement the same interface. This makes the client code compatible with any collection type or any traversal algorithm as long as thereβs a proper iterator.
Real-World Analogy
Section titled βReal-World AnalogyβYou plan to visit Rome for a few days and visit all the main sights and attractions. But once there, you could waste a lot of time walking in circles, unable to find even the Colosseum.
Instead, you could buy a virtual guide app for your smartphone and use it for navigation. Itβs smart and inexpensive, and you could stay at a sight as long as you want. Alternatively, you could spend some of the budget and hire a local guide who knows the city like the back of their hand. The guide would be your iterator. You can choose whichever βiteratorβ you prefer (app, human, or map) to traverse the cityβs sights.
Structure
Section titled βStructureβ- Iterator Interface declares the operations required for traversing a collection: fetching the next element, retrieving the current position, restarting iteration, etc..
- Concrete Iterators implement specific algorithms for traversing a collection. The iterator object should track the traversal progress on its own.
- Collection Interface declares one or multiple methods for getting iterators compatible with the collection.
- Concrete Collections return new instances of a particular concrete iterator class each time the client requests one.
- Client works with both collections and iterators via their interfaces. This way the client isnβt coupled to concrete classes, allowing you to use various collections and iterators with the same client code.
Structure (Class Diagram)
Section titled βStructure (Class Diagram)βclassDiagram
class IterableCollection {
<<interface>>
+createIterator() Iterator
}
class ConcreteCollection {
+createIterator() Iterator
}
class Iterator {
<<interface>>
+getNext()
+hasMore() bool
}
class ConcreteIterator {
-collection: ConcreteCollection
-iterationState
+getNext()
+hasMore() bool
}
IterableCollection <|-- ConcreteCollection
Iterator <|-- ConcreteIterator
ConcreteCollection ..> ConcreteIterator : creates
ConcreteIterator o-- ConcreteCollection
Pseudocode
Section titled βPseudocodeβ# The collection interface must declare a factory method for# producing iterators.interface SocialNetwork: method createFriendsIterator(profileId): Iterator method createCoworkersIterator(profileId): Iterator
# Each concrete collection is coupled to a set of concrete# iterator classes it returns.class Facebook implements SocialNetwork: method createFriendsIterator(profileId): return new FacebookIterator(this, profileId, "friends") method createCoworkersIterator(profileId): return new FacebookIterator(this, profileId, "coworkers")
# The common interface for all iterators.interface Iterator: method getNext(): Profile method hasMore(): boolean
# The concrete iterator class.class FacebookIterator implements Iterator: private field facebook: Facebook private field profileId, type: string private field currentPosition = 0 private field cache: array of Profile
constructor FacebookIterator(facebook, profileId, type): this.facebook = facebook this.profileId = profileId this.type = type
private method lazyInit(): if (cache == null): cache = facebook.socialGraphRequest(profileId, type)
method getNext(): if (hasMore()): currentPosition++ return cache[currentPosition]
method hasMore(): lazyInit() return currentPosition < cache.lengthApplicability
Section titled βApplicabilityβ- Hide Complex Internal Structures: Use the Iterator pattern when your collection has a complex data structure under the hood, but you want to hide its complexity from clients (for convenience or security).
- Reduce Code Duplication: Use the pattern to reduce duplication of the traversal code across your app.
- Uniform Traversal: Use the Iterator when you want your code to be able to traverse different data structures or when types of these structures are unknown beforehand.
How to Implement
Section titled βHow to Implementβ- Declare the iterator interface.
- Declare the collection interface and describe a method for fetching iterators.
- Implement concrete iterator classes for the collections that you want to be able to traverse with iterators.
- Implement the collection interface in your collection classes.
- Go over the client code to replace all of the collection traversal code with the use of iterators.
Pros and Cons
Section titled βPros and ConsβPros:
- Single Responsibility Principle: You can clean up the client code and the collections by extracting bulky traversal algorithms into separate classes.
- Open/Closed Principle: You can implement new types of collections and iterators and pass them to existing code without breaking anything.
- You can iterate over the same collection in parallel because each iterator object contains its own iteration state.
- You can delay an iteration and continue it when needed.
Cons:
- Applying the pattern can be an overkill if your app only works with simple collections.
- Using an iterator might be less efficient than going through elements of some specialized collections directly.
Relations with other Design Patterns
Section titled βRelations with other Design Patternsβ- You can use Iterators to traverse Composite trees.
- You can use Factory Method along with Iterator to let collection subclasses return different types of iterators that are compatible with the collections.
- You can use Memento along with Iterator to capture the current iteration state and roll it back if necessary.
- Visitor can be used along with Iterator to traverse a complex data structure and execute some operation over its elements, even if they all have different classes.
Code Examples
Section titled βCode ExamplesβCode Examples (C#)
Section titled βCode Examples (C#)β// Iteratorpublic interface IIterator<T>{ bool HasNext(); T Next();}
// Aggregatepublic interface IAggregate<T>{ IIterator<T> CreateIterator();}
// Concrete Aggregatepublic class NumberCollection : IAggregate<int>{ private readonly List<int> _numbers = new();
public void Add(int number) => _numbers.Add(number);
public IIterator<int> CreateIterator() => new NumberIterator(_numbers);}
// Concrete Iteratorpublic class NumberIterator : IIterator<int>{ private readonly List<int> _numbers; private int _position = 0;
public NumberIterator(List<int> numbers) { _numbers = numbers; }
public bool HasNext() => _position < _numbers.Count;
public int Next() => _numbers[_position++];}
// Usageclass Program{ static void Main() { var collection = new NumberCollection(); collection.Add(10); collection.Add(20); collection.Add(30);
var iterator = collection.CreateIterator();
while (iterator.HasNext()) Console.WriteLine(iterator.Next()); }}Code Examples (Python)
Section titled βCode Examples (Python)βfrom __future__ import annotationsfrom collections.abc import Iterable, Iteratorfrom typing import Any, List
class AlphabeticalOrderIterator(Iterator): """ Concrete Iterators implement various traversal algorithms. These classes store the current traversal position at all times. """ _position: int = None _reverse: bool = False
def __init__(self, collection: WordsCollection, reverse: bool = False) -> None: self._collection = collection self._reverse = reverse self._position = -1 if reverse else 0
def __next__(self): try: value = self._collection[self._position] self._position += -1 if self._reverse else 1 except IndexError: raise StopIteration()
return value
class WordsCollection(Iterable): """ Concrete Collections provide one or several methods for retrieving fresh iterator instances, compatible with the collection class. """ def __init__(self, collection: List[Any] = []) -> None: self._collection = collection
def __iter__(self) -> AlphabeticalOrderIterator: return AlphabeticalOrderIterator(self._collection)
def get_reverse_iterator(self) -> AlphabeticalOrderIterator: return AlphabeticalOrderIterator(self._collection, True)
def add_item(self, item: Any): self._collection.append(item)
if __name__ == "__main__": collection = WordsCollection() collection.add_item("First") collection.add_item("Second") collection.add_item("Third")
print("Straight traversal:") print("\n".join(collection)) print("")
print("Reverse traversal:") print("\n".join(collection.get_reverse_iterator()), end="")Code Examples (C++)
Section titled βCode Examples (C++)β#include <iostream>#include <vector>#include <memory>
// Iteratortemplate <typename T>class IIterator {public: virtual ~IIterator() = default; virtual bool HasNext() = 0; virtual T Next() = 0;};
// Aggregatetemplate <typename T>class IAggregate {public: virtual ~IAggregate() = default; virtual std::unique_ptr<IIterator<T>> CreateIterator() = 0;};
// Concrete Iteratorclass NumberIterator : public IIterator<int> {private: const std::vector<int>& numbers; size_t position = 0;
public: NumberIterator(const std::vector<int>& nums) : numbers(nums) {}
bool HasNext() override { return position < numbers.size(); }
int Next() override { return numbers[position++]; }};
// Concrete Aggregateclass NumberCollection : public IAggregate<int> {private: std::vector<int> numbers;
public: void Add(int number) { numbers.push_back(number); }
std::unique_ptr<IIterator<int>> CreateIterator() override { return std::make_unique<NumberIterator>(numbers); }};
// Usageint main() { NumberCollection collection; collection.Add(10); collection.Add(20); collection.Add(30);
auto iterator = collection.CreateIterator();
while (iterator->HasNext()) { std::cout << iterator->Next() << std::endl; }
return 0;}Code Examples (Java)
Section titled βCode Examples (Java)β// Iteratorpublic interface IIterator<T> { boolean hasNext(); T next();}
// Aggregatepublic interface IAggregate<T> { IIterator<T> createIterator();}
// Concrete Aggregateimport java.util.ArrayList;import java.util.List;
public class NumberCollection implements IAggregate<Integer> {
private final List<Integer> numbers = new ArrayList<>();
public void add(int number) { numbers.add(number); }
@Override public IIterator<Integer> createIterator() { return new NumberIterator(numbers); }}
// Concrete Iteratorimport java.util.List;
public class NumberIterator implements IIterator<Integer> {
private final List<Integer> numbers; private int position = 0;
public NumberIterator(List<Integer> numbers) { this.numbers = numbers; }
@Override public boolean hasNext() { return position < numbers.size(); }
@Override public Integer next() { return numbers.get(position++); }}
// Usagepublic class Program { public static void main(String[] args) { NumberCollection collection = new NumberCollection(); collection.add(10); collection.add(20); collection.add(30);
IIterator<Integer> iterator = collection.createIterator();
while (iterator.hasNext()) { System.out.println(iterator.next()); } }}Code Examples (JavaScript)
Section titled βCode Examples (JavaScript)β// Iterator (simulated interface)class IIterator { hasNext() { throw new Error("hasNext() must be implemented"); }
next() { throw new Error("next() must be implemented"); }}
// Aggregate (simulated interface)class IAggregate { createIterator() { throw new Error("createIterator() must be implemented"); }}
// Concrete Aggregateclass NumberCollection extends IAggregate { constructor() { super(); this.numbers = []; }
add(number) { this.numbers.push(number); }
createIterator() { return new NumberIterator(this.numbers); }}
// Concrete Iteratorclass NumberIterator extends IIterator { constructor(numbers) { super(); this.numbers = numbers; this.position = 0; }
hasNext() { return this.position < this.numbers.length; }
next() { return this.numbers[this.position++]; }}
// Usageconst collection = new NumberCollection();collection.add(10);collection.add(20);collection.add(30);
const iterator = collection.createIterator();
while (iterator.hasNext()) { console.log(iterator.next());}Code Examples (Visual Basic)
Section titled βCode Examples (Visual Basic)β' ============================' Iterator' ============================Public Interface IIterator(Of T) Function HasNext() As Boolean Function [Next]() As TEnd Interface
' ============================' Aggregate' ============================Public Interface IAggregate(Of T) Function CreateIterator() As IIterator(Of T)End Interface
' ============================' Concrete Aggregate' ============================Public Class NumberCollection Implements IAggregate(Of Integer)
Private ReadOnly _numbers As New List(Of Integer)()
Public Sub Add(number As Integer) _numbers.Add(number) End Sub
Public Function CreateIterator() As IIterator(Of Integer) _ Implements IAggregate(Of Integer).CreateIterator
Return New NumberIterator(_numbers) End FunctionEnd Class
' ============================' Concrete Iterator' ============================Public Class NumberIterator Implements IIterator(Of Integer)
Private ReadOnly _numbers As List(Of Integer) Private _position As Integer = 0
Public Sub New(numbers As List(Of Integer)) _numbers = numbers End Sub
Public Function HasNext() As Boolean Implements IIterator(Of Integer).HasNext Return _position < _numbers.Count End Function
Public Function [Next]() As Integer Implements IIterator(Of Integer).Next Dim value = _numbers(_position) _position += 1 Return value End FunctionEnd Class
' ============================' Usage' ============================Module Program Sub Main() Dim collection As New NumberCollection() collection.Add(10) collection.Add(20) collection.Add(30)
Dim iterator = collection.CreateIterator()
While iterator.HasNext() Console.WriteLine(iterator.Next()) End While End SubEnd ModuleCode Examples (Rust)
Section titled βCode Examples (Rust)β// Iteratortrait Iterator { fn has_next(&self) -> bool; fn next(&mut self) -> i32;}
// Aggregatetrait Aggregate { fn create_iterator(&self) -> Box<dyn Iterator>;}
// Concrete Aggregatestruct NumberCollection { numbers: Vec<i32>,}
impl NumberCollection { fn new() -> Self { Self { numbers: Vec::new() } }
fn add(&mut self, number: i32) { self.numbers.push(number); }}
impl Aggregate for NumberCollection { fn create_iterator(&self) -> Box<dyn Iterator> { Box::new(NumberIterator::new(self.numbers.clone())) }}
// Concrete Iteratorstruct NumberIterator { numbers: Vec<i32>, position: usize,}
impl NumberIterator { fn new(numbers: Vec<i32>) -> Self { Self { numbers, position: 0 } }}
impl Iterator for NumberIterator { fn has_next(&self) -> bool { self.position < self.numbers.len() }
fn next(&mut self) -> i32 { let value = self.numbers[self.position]; self.position += 1; value }}
// Usagefn main() { let mut collection = NumberCollection::new(); collection.add(10); collection.add(20); collection.add(30);
let mut iterator = collection.create_iterator();
while iterator.has_next() { println!("{}", iterator.next()); }}Code Examples (PHP)
Section titled βCode Examples (PHP)β<?php
// Iteratorinterface IIterator{ public function hasNext(): bool; public function next();}
// Aggregateinterface IAggregate{ public function createIterator(): IIterator;}
// Concrete Aggregateclass NumberCollection implements IAggregate{ private array $numbers = [];
public function add(int $number): void { $this->numbers[] = $number; }
public function createIterator(): IIterator { return new NumberIterator($this->numbers); }}
// Concrete Iteratorclass NumberIterator implements IIterator{ private array $numbers; private int $position = 0;
public function __construct(array $numbers) { $this->numbers = $numbers; }
public function hasNext(): bool { return $this->position < count($this->numbers); }
public function next() { return $this->numbers[$this->position++]; }}
// Usage$collection = new NumberCollection();$collection->add(10);$collection->add(20);$collection->add(30);
$iterator = $collection->createIterator();
while ($iterator->hasNext()) { echo $iterator->next() . "\n";}Code Examples (Go)
Section titled βCode Examples (Go)βpackage main
import ( "fmt")
// Iteratortype Iterator interface { HasNext() bool Next() int}
// Aggregatetype Aggregate interface { CreateIterator() Iterator}
// Concrete Aggregatetype NumberCollection struct { numbers []int}
func NewNumberCollection() *NumberCollection { return &NumberCollection{numbers: []int{}}}
func (c *NumberCollection) Add(n int) { c.numbers = append(c.numbers, n)}
func (c *NumberCollection) CreateIterator() Iterator { return &NumberIterator{ numbers: c.numbers, position: 0, }}
// Concrete Iteratortype NumberIterator struct { numbers []int position int}
func (it *NumberIterator) HasNext() bool { return it.position < len(it.numbers)}
func (it *NumberIterator) Next() int { val := it.numbers[it.position] it.position++ return val}
// Usagefunc main() { collection := NewNumberCollection() collection.Add(10) collection.Add(20) collection.Add(30)
iterator := collection.CreateIterator()
for iterator.HasNext() { fmt.Println(iterator.Next()) }}Code Examples (Ruby)
Section titled βCode Examples (Ruby)β# Iteratorclass Iteratordef has_next raise NotImplementedError, "Subclasses must implement has_next"end
def next raise NotImplementedError, "Subclasses must implement next"endend
# Aggregateclass Aggregatedef create_iterator raise NotImplementedError, "Subclasses must implement create_iterator"endend
# Concrete Aggregateclass NumberCollection < Aggregatedef initialize @numbers = []end
def add(number) @numbers << numberend
def create_iterator NumberIterator.new(@numbers)endend
# Concrete Iteratorclass NumberIterator < Iteratordef initialize(numbers) @numbers = numbers @position = 0end
def has_next @position < @numbers.lengthend
def next value = @numbers[@position] @position += 1 valueendend
# Usagecollection = NumberCollection.newcollection.add(10)collection.add(20)collection.add(30)
iterator = collection.create_iterator
while iterator.has_nextputs iterator.nextendCode Examples (Perl)
Section titled βCode Examples (Perl)β#!/usr/bin/perluse strict;use warnings;
############################# Iterator (Interface-like)############################package Iterator;
sub has_next { die "has_next() must be implemented by subclass";}
sub next { die "next() must be implemented by subclass";}
############################# Aggregate (Interface-like)############################package Aggregate;
sub create_iterator { die "create_iterator() must be implemented by subclass";}
############################# Concrete Aggregate############################package NumberCollection;our @ISA = ('Aggregate');
sub new { my ($class) = @_; return bless { numbers => [] }, $class;}
sub add { my ($self, $number) = @_; push @{ $self->{numbers} }, $number;}
sub create_iterator { my ($self) = @_; return NumberIterator->new($self->{numbers});}
############################# Concrete Iterator############################package NumberIterator;our @ISA = ('Iterator');
sub new { my ($class, $numbers) = @_; return bless { numbers => $numbers, position => 0 }, $class;}
sub has_next { my ($self) = @_; return $self->{position} < @{ $self->{numbers} };}
sub next { my ($self) = @_; return $self->{numbers}->[ $self->{position}++ ];}
############################# Usage############################package main;
my $collection = NumberCollection->new();$collection->add(10);$collection->add(20);$collection->add(30);
my $iterator = $collection->create_iterator();
while ($iterator->has_next) { print $iterator->next, "\n";}Code Examples (TypeScript)
Section titled βCode Examples (TypeScript)β// Iteratorinterface IIterator<T> { hasNext(): boolean; next(): T;}
// Aggregateinterface IAggregate<T> { createIterator(): IIterator<T>;}
// Concrete Aggregateclass NumberCollection implements IAggregate<number> { private numbers: number[] = [];
add(number: number): void { this.numbers.push(number); }
createIterator(): IIterator<number> { return new NumberIterator(this.numbers); }}
// Concrete Iteratorclass NumberIterator implements IIterator<number> { private numbers: number[]; private position: number = 0;
constructor(numbers: number[]) { this.numbers = numbers; }
hasNext(): boolean { return this.position < this.numbers.length; }
next(): number { return this.numbers[this.position++]; }}
// Usageconst collection = new NumberCollection();collection.add(10);collection.add(20);collection.add(30);
const iterator = collection.createIterator();
while (iterator.hasNext()) { console.log(iterator.next());}