Iterator
Callers loop over shelf._books[position] directly.
Let the collection hand out a small iterator object that remembers the current position and offers has_next() and next().
You want to hide the inner structure of a collection
The picture from life
A TV remote's "next channel" button. You do not need to know how the channels are stored inside the box; you just press next until there are no more. A different brand of TV can store them differently, and the button still works the same.
The problem
What goes wrong when you do not use it.
Callers loop over shelf._books[position] directly. If you switch the shelf from a list to a tree, every loop in the program breaks, and you cannot offer a second way to walk (like backwards) without more index tricks.
- calls / uses
- You want to hide the inner structure of a collection
- There are several ways to walk it (forwards, backwards, filtered)
- Different collections should be walkable by the same loop
The idea
How the pattern fixes it.
Let the collection hand out a small iterator object that remembers the current position and offers has_next() and next(). Callers only use these two, so the storage can change freely.
- calls / uses
- creates
Who is who
- Shelf (aggregate)
- Holds books; creates an iterator.
- ReverseIterator
- Remembers a position and returns the next book.
- Client
- Loops with for/next and sees no storage.
Build it in steps
- 1Define the iterator interface: has_next, next.
- 2Write an iterator that keeps the position.
- 3Let the collection return one.
- 4Callers loop with it.
Watch it run
Step through the calls. Tap an arrow to jump to it.
The client asks the shelf for an iterator.
iter(shelf)The code
Python and Java. Try writing the classes yourself from the diagram first.
class ReverseIterator:
def __init__(self, books):
self.books = books
self.position = len(books) - 1
def __iter__(self):
return self
def __next__(self):
if self.position < 0:
raise StopIteration
book = self.books[self.position]
self.position -= 1
return book
class Shelf:
def __init__(self):
self._books = []
def add(self, book):
self._books.append(book)
def __iter__(self): # the collection hands out an iterator
return ReverseIterator(self._books)
def demo():
shelf = Shelf()
for title in ("Dune", "Emma", "Ulysses"):
shelf.add(title)
return [book for book in shelf]What you gain
- Callers do not depend on how items are stored
- Several walks can run at once, each with its own position
What it costs
- Overkill for a plain list
- Changing the collection while iterating can surprise you
You have already met it in
- Python's for loop and iter()/next()
- Java's Iterable and the enhanced for
- Database cursors
Often mixed up with
Make it stick
Write a Playlist with a shuffle iterator and a normal iterator. Loop over the same playlist with both.