TypeScript Iterator Pattern Example

1. Definition

The Iterator Design Pattern provides a way to access the elements of a collection object in a sequential manner without exposing its underlying representation. The main goal is to treat collections of objects and individual objects uniformly when being traversed.

2. Problem Statement

When dealing with complex data structures, direct access to its elements can become cumbersome, and sometimes the client shouldn't have direct access to the collection's internal structure. Additionally, you may want to provide different ways of iterating over the same data structure.

3. Solution

The Iterator pattern solves this by providing a standardized way to traverse the collection without needing to know about its intricate details. It decouples the logic of iteration from the actual data structures.

4. Real-World Use Cases

1. Navigating through pages of a book.

2. Browsing items in a playlist.

3. Traversing tree or graph structures.

5. Implementation Steps

1. Define an Iterator interface that declares methods for traversal operations.

2. Implement a ConcreteIterator that implements the Iterator interface.

3. Define an Aggregate (Collection) interface that declares a createIterator() method.

4. Implement a ConcreteAggregate that implements the Aggregate interface and returns an instance of ConcreteIterator.

6. Implementation in TypeScript

// Step 1: Iterator Interface
interface Iterator<T> {
    next(): T | null;
    hasNext(): boolean;
}
// Step 2: Concrete Iterator
class ConcreteIterator implements Iterator<string> {
    private collection: string[];
    private position: number = 0;
    constructor(collection: string[]) {
        this.collection = collection;
    }
    next(): string | null {
        if (this.hasNext()) {
            return this.collection[this.position++];
        } else {
            return null;
        }
    }
    hasNext(): boolean {
        return this.position < this.collection.length;
    }
}
// Step 3: Aggregate Interface
interface Aggregate {
    createIterator(): Iterator<string>;
}
// Step 4: Concrete Aggregate
class WordsCollection implements Aggregate {
    private items: string[] = [];
    getItems(): string[] {
        return this.items;
    }
    addItem(item: string): void {
        this.items.push(item);
    }
    createIterator(): Iterator<string> {
        return new ConcreteIterator(this.items);
    }
}
// Client code
const collection = new WordsCollection();
collection.addItem("Word1");
collection.addItem("Word2");
collection.addItem("Word3");
const iterator = collection.createIterator();
while (iterator.hasNext()) {
    console.log(iterator.next());
}

Output:

Word1
Word2
Word3

Explanation:

The above example demonstrates the Iterator pattern with a collection of words. The WordsCollection class represents a concrete aggregate, and the ConcreteIterator provides the mechanism to iterate over this collection. The client code then uses this iterator to traverse and print the items of the collection.

7. When to use?

Use the Iterator pattern when:

1. You want to provide a uniform way to access different collections of objects.

2. You want to encapsulate the internal structure of a collection.

3. You want to provide multiple ways of traversal over the same collection.


Comments