In the "Structure" section of the Iterator design pattern, the book Design Patterns: Elements of Reusable Object-Oriented Software defines the Iterator
class with four basic operations: First
, Next
, IsDone
and CurrentItem
.
In the "Implementation" section, the book contains the following item:
Who defines the traversal algorithm? The iterator is not the only place where the traversal algorithm can be defined. The aggregate might define the traversal algorithm and use the iterator to store just the state of the iteration. We call this kind of iterator a cursor, since it merely points to the current position in the aggregate. A client will invoke the
Next
operation on the aggregate with the cursor as an argument, and theNext
operation will change the state of the cursor.If the iterator is responsible for the traversal algorithm, then it’s easy to use different iteration algorithms on the same aggregate, and it can also be easier to reuse the same algorithm on different aggregates. On the other hand, the traversal algorithm might need to access the private variables of the aggregate. If so, putting the traversal algorithm in the iterator violates the encapsulation of the aggregate.
But no example implementation of a cursor iterator is provided.
Who should expose the interface for controlling the iteration, the cursor iterator or the aggregate?
Here after is how I would implement the two alternatives in Python.
If the cursor iterator exposes the iteration interface, in addition to the
GetState
andSetState
operations, it should define theFirst
,Next
,IsDone
andCurrentItem
basic operations and delegate to the corresponding aggregate operations:class Iterator: def __init__(self, aggregate): self.__aggregate = aggregate def first(self): self.__aggregate._first(self) def next(self): self.__aggregate._next(self) def is_done(self): return self.__aggregate._is_done(self) def current_item(self): return self.__aggregate._current_item(self) def _get_state(self): return self.__state def _set_state(self, state): self.__state = state
And the aggregate should define the same basic operations which retrieve and update the cursor iterator state using its
GetState
andSetState
operations:class Aggregate: def __init__(self, list): self.__list = list def _first(self, iterator): iterator._set_state(0) def _next(self, iterator): iterator._set_state(iterator._get_state() + 1) def _is_done(self, iterator): return iterator._get_state() >= len(self.__list) def _current_item(self, iterator): if self._is_done(iterator): raise ValueError return self.__list[iterator._get_state()] def create_iterator(self): return Iterator(self)
The client would then use the cursor iterator to control the iteration:
aggregate = Aggregate(["foo", "bar", "baz", "qux"]) iterator = aggregate.create_iterator() iterator.first() while not iterator.is_done(): print(iterator.current_item()) iterator.next()
If the aggregate exposes the iteration interface, the cursor iterator should define the
GetState
andSetState
operations:class Iterator: def _get_state(self): return self.__state def _set_state(self, state): self.__state = state
And the aggregate should define the
First
,Next
,IsDone
andCurrentItem
basic operations which retrieve and update the cursor iterator state using itsGetState
andSetState
operations:class Aggregate: def __init__(self, list): self.__list = list def first(self, iterator): iterator._set_state(0) def next(self, iterator): iterator._set_state(iterator._get_state() + 1) def is_done(self, iterator): return iterator._get_state() >= len(self.__list) def current_item(self, iterator): if self.is_done(iterator): raise ValueError return self.__list[iterator._get_state()] def create_iterator(self): return Iterator()
The client would then use the aggregate to control the iteration:
aggregate = Aggregate(["foo", "bar", "baz", "qux"]) iterator = aggregate.create_iterator() aggregate.first(iterator) while not aggregate.is_done(iterator): print(aggregate.current_item(iterator)) aggregate.next(iterator)