Skip to main content

Iterator Pattern

The Iterator Pattern is like scrolling through a playlist on your music app – it provides a way to access elements of a collection one by one without needing to know the collection's internal structure. It allows you to move through items in a systematic manner, making it easier to iterate over various data structures like lists or arrays. It's a convenient tool for navigating and processing elements in a collection without worrying about the underlying details.

Definition

The Iterator Pattern is a behavioral design pattern that provides a way to sequentially access elements of a collection without exposing its underlying representation. It involves defining an interface for iterating over elements and implementing it in concrete iterator classes. Clients use an iterator to traverse the collection, abstracting away the complexities of the collection's structure and providing a uniform way to access its elements.

Considerations

Considerations for the Iterator Pattern include designing a clear and consistent iterator interface to work with different types of collections. It's essential to ensure that the iterators are synchronized with the state of the collection, especially in cases where the collection can change dynamically. Developers should pay attention to the performance implications of using iterators, considering factors like lazy versus eager loading of elements.

Benefits

The Iterator Pattern promotes a separation of concerns by isolating the iteration logic from the collection, making both more flexible. It simplifies the client code by providing a common interface for traversing various collections. This pattern allows for the creation of multiple iterators for the same collection, each maintaining its own iteration state, enhancing versatility.

Trade-offs

While the Iterator Pattern simplifies client code, it may introduce additional complexity in implementing iterators for different collection types. It might not be suitable for simple collections or cases where iterating over the entire collection is not a common use case. Developers need to weigh the benefits of abstraction and flexibility against the potential drawbacks of increased design complexity.

Practical Use-Case

Iterating Over a Database Result Set:
In database systems, the Iterator Pattern can be applied to traverse rows in a result set. It allows clients to access database records one by one without needing to know the intricacies of the database structure or how the records are retrieved.

Code Example

from abc import ABC, abstractmethod

# Iterator: Iterator
class Iterator(ABC):
@abstractmethod
def has_next(self):
pass

@abstractmethod
def next(self):
pass

# Concrete Iterator: ConcreteIterator
class ConcreteIterator(Iterator):
def __init__(self, collection):
self.collection = collection
self.index = 0

def has_next(self):
return self.index < len(self.collection)

def next(self):
if self.has_next():
item = self.collection[self.index]
self.index += 1
return item
else:
raise StopIteration("End of collection reached")

# Aggregate: Aggregate
class Aggregate(ABC):
@abstractmethod
def create_iterator(self):
pass

# Concrete Aggregate: ConcreteAggregate
class ConcreteAggregate(Aggregate):
def __init__(self):
self.items = []

def add_item(self, item):
self.items.append(item)

def create_iterator(self):
return ConcreteIterator(self.items)

# Client Code
aggregate = ConcreteAggregate()
aggregate.add_item("Item 1")
aggregate.add_item("Item 2")
aggregate.add_item("Item 3")

iterator = aggregate.create_iterator()

while iterator.has_next():
print(iterator.next())

In this example, the Iterator Pattern is demonstrated with Iterator as the abstract iterator, ConcreteIterator as the concrete iterator, Aggregate as the abstract aggregate, and ConcreteAggregate as the concrete aggregate. The client code uses the iterator to traverse the elements of the aggregate, providing a uniform way to access them regardless of the underlying collection type.