Figure 1. A Linked List
Figure 2. A Tree
Gamma et al. 95
Provide a way to traverse a collection of elements without exposing its underlying representation: array, linked list, tree, etc.
An aggregate object should give you a way to access its elements without exposing its internal structure.
The collection structure should be independent from the way it is traversed.
Move the collection traversal behavior into a separate class called Iterator.
All iterators should provide the same interface for accessing and traversing elements, regardless of the class of the collection object and the kind of traversal that is performed.
defines an interface for accessing and traversing elements
implements the Iterator interface and keeps track of the current position in the traversal of the aggregate
defines an interface for creating an Iterator object
creates and returns an instance of the proper ConcreteIterator
It supports variations in the traversal of an Aggregate: you can use different ways of iterating by defining various concrete subclasses of Iterator for your collection.
Iterators simplify the Aggregate interface
More than one traversal can be pending on an Aggregate
Iterators are widely used in the Java Collection Framework (JFC)!
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(E e);
}
If it’s the client: External Iterator
It it’s the iterator: Internal Iterator
// External:
Iterator it = list.iterator();
while (iterator.hasNext())
System.out.println(it.next());
// Internal:
list.stream().forEach(System.out::println);
more flexible: easy to compare two collections for equality
hard to implement on languages without anonymous functions or closures
easier to use
The Iterator only stores the state of the iteration (it’s called a cursor)
The Client invokes the next()
operation on the Aggregate, with the cursor as an argument
The next()
operation changes the state of the cursor
Easy to use different iteration algorithms on the same Aggregate
Maybe easier to reuse the same algorithm on different Aggregates
The Iterator might need to access the private variables of the Aggregate, violating the encapsulation of the Aggregate.
What happens if the Aggregate is modified when an Iterator is traversing it?
ensures that insertions and removals won’t interfere with traversal, without copying the Aggregate
in most implementations, robust Iterators are registered with the Aggregate:
on changes, the Aggregate adjusts its Iterators
Java Collections have a modification counter, called modCount
The counter is incremented after each add or removal
When created, Iterators copy the counter
The Iterator compares both counters before each iteration
First()
, Next()
, IsDone()
, and CurrentItem()
.
Previous()
and SkipTo()
The Iterator is a friend of its Aggregate
The Iterator is an inner class of its Aggregate, which implements an external Interface
.
It’s hard to implement an external Iterator over recursive Aggregates, like the Composite pattern.
Different traversal algorithms: Preorder, postorder, inorder, and breadth-first
Sometimes, it’s easier to implement an internal Iterator, and perform recursive calls (the path is stored in the call stack)
If the nodes in a Composite have an interface for moving from a node to its siblings, parents, and children: then a cursor-based Iterator may offer a better alternative.
A NullIterator
is always done with traversal: operation IsDone()
always evaluates to true.
NullIterators
are helpful for handling boundary conditions.
Useful for tree-structured aggregates.