Iterator Pattern

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

Understanding the Iterator Pattern

The Iterator pattern is a behavioral design pattern that provides a way to access the elements of a collection object sequentially without exposing its underlying representation. It separates the traversal algorithm from the data structure, allowing for multiple ways to iterate over the same collection.

Problem

Imagine you have a custom collection class (like a tree structure, graph, or specialized list) and you want to provide a way for clients to access its elements without exposing the internal structure. Additionally, you might want to support different traversal methods (like depth-first vs breadth-first for trees) without cluttering the collection class with traversal code.

Traditionally, collection classes have provided methods for accessing elements, but this approach has several issues:

Solution

The Iterator pattern suggests extracting the traversal behavior of a collection into a separate object called an iterator. This iterator defines a standard interface for accessing collection elements sequentially, typically with methods like hasNext() and next().

The collection provides a method for creating an iterator object, allowing clients to use that iterator to traverse the elements. This separation enables multiple iterators to operate independently on the same collection, even supporting different traversal algorithms.

Structure

Iterator Pattern Structure

Participants

When to Use

Use the Iterator Pattern when:

Benefits

Real-World Uses

Implementation Example

Here's a JavaScript implementation of the Iterator pattern for different types of collections:

// Iterator Interface (implicit in JavaScript)
// - hasNext(): boolean
// - next(): Element

// Concrete Iterator for Array
class ArrayIterator {
    constructor(array) {
        this.array = array;
        this.index = 0;
    }
    
    hasNext() {
        return this.index < this.array.length;
    }
    
    next() {
        if (this.hasNext()) {
            return this.array[this.index++];
        }
        return null;
    }
    
    // Additional methods that could be useful
    reset() {
        this.index = 0;
    }
    
    current() {
        return this.array[this.index];
    }
}

// Concrete Iterator for TreeNode (depth-first)
class DepthFirstIterator {
    constructor(rootNode) {
        this.stack = [rootNode];
        this.visited = new Set();
    }
    
    hasNext() {
        return this.stack.length > 0;
    }
    
    next() {
        if (!this.hasNext()) {
            return null;
        }
        
        const node = this.stack.pop();
        this.visited.add(node);
        
        // Add children in reverse order so they'll be processed in the correct order
        if (node.children) {
            for (let i = node.children.length - 1; i >= 0; i--) {
                const child = node.children[i];
                if (!this.visited.has(child)) {
                    this.stack.push(child);
                }
            }
        }
        
        return node;
    }
}

// Concrete Collection implementing Aggregate
class Collection {
    constructor(items = []) {
        this.items = items;
    }
    
    // Factory method that creates an iterator
    createIterator() {
        return new ArrayIterator(this.items);
    }
    
    // Collection-specific methods
    add(item) {
        this.items.push(item);
    }
    
    remove(item) {
        const index = this.items.indexOf(item);
        if (index >= 0) {
            this.items.splice(index, 1);
        }
    }
}

// Example Tree structure
class TreeNode {
    constructor(value) {
        this.value = value;
        this.children = [];
    }
    
    addChild(child) {
        this.children.push(child);
        return this;
    }
    
    // Factory method that creates a depth-first iterator
    createIterator() {
        return new DepthFirstIterator(this);
    }
}

// Client code
function clientCode() {
    console.log("Iterating through a collection:");
    const collection = new Collection([10, 20, 30, 40, 50]);
    const iterator = collection.createIterator();
    
    while (iterator.hasNext()) {
        console.log(iterator.next());
    }
    
    console.log("\nIterating through a tree structure (depth-first):");
    const root = new TreeNode('A');
    root.addChild(new TreeNode('B'))
        .addChild(new TreeNode('C'));
    
    root.children[0].addChild(new TreeNode('D'))
                    .addChild(new TreeNode('E'));
    root.children[1].addChild(new TreeNode('F'));
    
    const treeIterator = root.createIterator();
    while (treeIterator.hasNext()) {
        const node = treeIterator.next();
        console.log(node.value);
    }
}

clientCode();

In this example:

This implementation showcases how the Iterator pattern allows different traversal strategies to be encapsulated in iterator objects, keeping the collections cleaner and client code simpler.

Interactive Demo

Experience the Iterator pattern in action with this interactive demo. You can create different types of collections and use iterators to traverse them.

Interactive Demo: Iterating Through Collections

Create a collection and use different iterators to traverse it sequentially.

Array Collection

Iterator Controls:

Current Item:

No iterator created

Iteration Log: