0 XP · 0 scenes

Iterator

Walk through a collection without knowing how it is stored
The problem

Callers loop over shelf._books[position] directly.

The idea

Let the collection hand out a small iterator object that remembers the current position and offers has_next() and next().

Reach for it when

You want to hide the inner structure of a collection

1

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.

2

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.

✗ Before
Clientfor i in range(len(shelf._books)): print(shelf._books[i])Shelf_books ← privatereaches inside
  • calls / uses
The caller reaches into the shelf's private list. If the shelf changes how it stores books, every caller breaks.
Reach for it when
  • 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
3

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.

✓ After
Shelfadd(book)__iter__()ReverseIterator__next__()Clientcreatesiter(shelf)next()
  • calls / uses
  • creates
The shelf hands out an iterator. The client just asks for the next item and never sees the storage.

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

  1. 1Define the iterator interface: has_next, next.
  2. 2Write an iterator that keeps the position.
  3. 3Let the collection return one.
  4. 4Callers loop with it.
4

Watch it run

Step through the calls. Tap an arrow to jump to it.

ClientShelfReverseIterator1iter(shelf)2ReverseIterator(books)3iterator4next()5'Ulysses'6next()7'Emma'
Step 1/7

The client asks the shelf for an iterator.

iter(shelf)
5

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

  • CompositeComposite builds a tree; Iterator is how you walk any structure, trees included.
  • VisitorIterator decides the order of walking; Visitor decides what to do at each stop.
6

Make it stick

Write a Playlist with a shuffle iterator and a normal iterator. Loop over the same playlist with both.

Confidence