Iterator
Move the job of walking a collection out of the collection and into a separate object, so clients traverse anything without learning how it is stored.
The problem
A collection is a container for a group of objects, and whatever it is built on - list, stack, tree, graph, a REST API pretending to be a graph - it has to let other code reach the elements, once each.
Over a list that is a for-loop. Over a tree it is a question: depth-first today, breadth-first next week, something bespoke the week after. Pile all of those into the collection class and you have blurred its actual responsibility, which is storing data efficiently. Some of those walks are application-specific anyway and have no business living in a general-purpose container.
Meanwhile the client code usually does not care how anything is stored - but since every collection exposes access differently, it ends up coupled to concrete collection classes regardless.
The solution
Move the traversal behavior into a separate object: the iterator. Besides the walking algorithm, it holds all the bookkeeping - where you are, how much is left, any cached page of results. Because that state belongs to the iterator rather than the collection, several iterators can crawl the same collection simultaneously without interfering.
An iterator typically offers one primary method for fetching the next element; the client keeps calling until there is nothing left. All iterators implement the same interface, so client code works with any collection and any traversal for which an iterator exists. Need a special walk? Write one iterator class. The collection and the client stay untouched.
- 1The app asks the collection for an iterator. The collection picks the concrete class; the app only sees the interface type.
- 2The iterator gets a reference back to the collection it will traverse, plus the parameters that define this particular walk.
- 3The client is handed the iterator, not the network. It can walk profiles and do nothing else - and it never learns which social network this is.
- 4The client drives the loop with two questions and no knowledge of how profiles are fetched.
- 5First call only: the iterator lazily fetches and caches the page of profiles. Authentication, REST, paging - all of it hides here.
- 6The iterator advances its own position and returns a profile. That position belongs to the iterator, so a second iterator over the same profile is unaffected.
- 7Loop until hasMore() says stop. Swap in the friends iterator tomorrow and this client code does not change a character.
Structure
Note the collection interface: it declares methods that return iterators, and their return type is the iterator interface. That is a Factory Method doing quiet work, and it is why a concrete collection can hand back whichever concrete iterator it wants without anyone downstream noticing.
Code
Same example three ways: walking profiles in a social graph, friends or coworkers, over whichever network is configured.
When to use it
- The collection has a complicated structure underneath and you want clients spared the details, for convenience or to keep careless code from mangling it.
- The same bulky traversal logic is being retyped across your app. Move it into iterators and both the client and the collection get leaner.
- Client code must handle several data structures, or structures you cannot name yet. Two interfaces - collection and iterator - are all it has to know.
Pitfalls
- Overkill on simple data. If your collections are lists and your traversals are for-loops, an iterator hierarchy adds classes and subtracts clarity.
- A tax on performance. Going through an iterator is usually slower than indexing a specialized collection directly. Usually irrelevant, occasionally decisive.
- Mutation during iteration. The collection can change under an iterator's feet. Real libraries answer this with fail-fast errors or snapshot semantics; decide which one you are promising before someone finds out the hard way.
- Off-by-one in the cursor. Whether the position starts before the first element or on it decides what hasMore() must compare. Getting it backwards silently drops or repeats an element.
Don't confuse it with
- Composite. Composite gives you the tree, Iterator gives you the walk. A leaf-to-root or root-to-leaf traversal over a Composite is the textbook use.
- Factory Method. Not a rival - a component.
createIterator()is a factory method, which is how collection subclasses return iterators matched to themselves. - Visitor. Iterator controls the order of arrival; Visitor controls what you do on arrival. Combine them to run an operation over a heterogeneous structure.
- Memento. Pair them to snapshot an iteration's position and roll back to it later.
- Language-level iterators. Python's
__iter__, Java'sIterable, C#'sIEnumerableare this pattern baked into the runtime. You have been using it for years; this is just the name for it.
Check yourself
Where does the traversal state - current position, what is left - actually live?