Iterator Pattern
title: “Iterator Pattern” date: 2024-07-12 categories: - Design Patterns tags: - design patterns - behavioral patterns - iterator - collections bookHidden: false
The Iterator pattern is the reason you can loop over a collection without knowing or caring how it stores its data.
It’s a behavioral pattern that provides a standard way to traverse elements in a collection while hiding the underlying representation. Traversal logic lives in its own object, so collections stay focused on storage and iterators handle walking through items.
Problem and Solution
Problem
Suppose you have a complex data structure, such as a custom list or tree. You want to provide a way for clients to access elements in this structure sequentially, but exposing the internal details of the collection would lead to a tightly coupled and inflexible design.
Solution
The Iterator pattern addresses this by creating a separate iterator object that can traverse the collection. The iterator knows how to navigate the collection and provides methods for accessing elements sequentially, making it easy for the client to interact with the collection without knowing its structure.
Structure
The Iterator pattern typically includes:
- Iterator Interface: Defines the methods for traversing a collection, such as
nextandhasNext. - Concrete Iterator: Implements the iterator interface, providing the logic for traversing a specific collection.
- Aggregate (Collection) Interface: Defines the method for creating an iterator.
- Concrete Aggregate (Concrete Collection): Implements the collection interface and returns an iterator for the specific collection.
UML Diagram
+-------------------+ +-------------------------+
| Iterator |<----------| ConcreteIterator |
|-------------------| +-------------------------+
| + hasNext() | | + hasNext() |
| + next() | | + next() |
+-------------------+ +-------------------------+
^
|
+-------------------+ +-------------------------+
| Collection |<----------| ConcreteCollection |
|-------------------| +-------------------------+
| + createIterator()| | + createIterator() |
+-------------------+ +-------------------------+Example: Custom Collection with Iterator
Let’s implement a custom collection of numbers using the Iterator pattern. We’ll create an IntegerCollection with a NumberIterator to allow sequential access to the numbers.
Step 1: Define the Iterator Interface
The Iterator interface defines methods for traversal: hasNext to check if there are more elements and next to retrieve the next element.
// Iterator Interface
interface Iterator<T> {
boolean hasNext();
T next();
}Step 2: Implement the Concrete Iterator
The NumberIterator class implements the Iterator interface to traverse an IntegerCollection.
// Concrete Iterator
class NumberIterator implements Iterator<Integer> {
private IntegerCollection collection;
private int position = 0;
public NumberIterator(IntegerCollection collection) {
this.collection = collection;
}
@Override
public boolean hasNext() {
return position < collection.size();
}
@Override
public Integer next() {
return hasNext() ? collection.get(position++) : null;
}
}Step 3: Define the Collection Interface
The Collection interface defines a method for creating an iterator, allowing clients to retrieve an iterator for traversing the collection.
// Collection Interface
interface Collection<T> {
Iterator<T> createIterator();
}Step 4: Implement the Concrete Collection
The IntegerCollection class implements the Collection interface, storing integers and returning an iterator for traversal.
// Concrete Collection
class IntegerCollection implements Collection<Integer> {
private List<Integer> numbers = new ArrayList<>();
public void add(Integer number) {
numbers.add(number);
}
public Integer get(int index) {
return numbers.get(index);
}
public int size() {
return numbers.size();
}
@Override
public Iterator<Integer> createIterator() {
return new NumberIterator(this);
}
}Step 5: Client Code Using the Iterator
The client code uses the iterator provided by IntegerCollection to access elements sequentially without knowing the collection’s internal structure.
public class Client {
public static void main(String[] args) {
IntegerCollection collection = new IntegerCollection();
collection.add(1);
collection.add(2);
collection.add(3);
Iterator<Integer> iterator = collection.createIterator();
System.out.println("Iterating through the collection:");
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}Output
Iterating through the collection:
1
2
3In this example:
- The
IntegerCollectionclass provides thecreateIteratormethod, allowing the client to obtain aNumberIterator. - The
NumberIteratortraverses theIntegerCollectionsequentially, implementing thehasNextandnextmethods. - The client code iterates through the collection without knowing its internal structure, relying solely on the iterator.
Applicability
Use the Iterator pattern when:
- You need to traverse a collection without exposing its underlying representation.
- You want to support different types of traversals (e.g., forward, backward) for a collection.
- You need to provide a uniform interface for traversing different types of collections.
Advantages and Disadvantages
Advantages
- Encapsulation of Traversal Logic: The Iterator pattern encapsulates the traversal logic, allowing the collection’s structure to remain hidden from the client.
- Simplifies Collection Interaction: The pattern provides a standard interface for iterating over collections, making it easier to interact with complex data structures.
- Supports Multiple Iterations: Multiple iterators can be created for the same collection, enabling multiple independent traversals.
Disadvantages
- Increased Complexity: The pattern introduces additional classes (e.g., iterators), which can add complexity, especially for simple collections.
- Potential for Concurrent Modification Issues: Iterators may encounter issues if the collection is modified during iteration, requiring additional safeguards or implementations for concurrent modification handling.
Best Practices for Implementing the Iterator Pattern
- Use the Iterator Pattern for Complex Collections: Apply this pattern when collections have complex structures or traversal requirements. For simpler collections, native iteration mechanisms may suffice.
- Provide Clear Traversal Methods: Ensure that
hasNextandnextmethods are intuitive and handle edge cases (e.g., reaching the end of the collection) gracefully. - Handle Concurrent Modifications Carefully: If the collection might be modified during iteration, implement safeguards like fail-fast or fail-safe iterators.
Conclusion
The Iterator pattern provides a flexible way to access elements in a collection sequentially without exposing its underlying structure. This pattern enables uniform traversal methods, supporting complex or custom collections while keeping the client code simple and decoupled from collection details.