098
behavioral design patterns

Iterator

Reference Wikipedia ↗
Iterator — sequence diagram
Plate 098 sequence diagram

The Iterator pattern is a behavioral design pattern that provides a way to access the elements of an aggregate object (like a list, set, or tree) sequentially without exposing its underlying representation. It defines a standard interface for creating iterators, allowing clients to traverse and manipulate elements without knowing how the aggregate is organized.

This pattern decouples the algorithms for traversing a collection from the collection itself. It promotes loose coupling between the collection and the code that uses it, making the collection more flexible and easier to maintain. Iterators also allow for multiple traversals of the same collection concurrently.

Usage

The Iterator pattern is commonly used in scenarios where:

  • You need to provide a way to access the elements of a collection without exposing its internal structure.
  • You want to support multiple traversal modes for a collection (e.g., forward, backward, skipping elements).
  • You need to iterate over complex data structures like trees or graphs.
  • You want to allow clients to iterate over a collection while it is being modified (with appropriate synchronization).

Examples

  1. Java Collections Framework: The java.util.Iterator interface and its implementations (e.g., ListIterator, Iterator<E> for various collection types like ArrayList, LinkedList, HashSet) are a prime example. You can iterate over these collections using a for-each loop, which internally utilizes the iterator pattern.

  2. Python Generators: Python’s generators, created using the yield keyword, effectively implement the iterator pattern. A generator function returns an iterator object that produces values on demand, rather than storing the entire sequence in memory. This is particularly useful for large datasets.

  3. C++ Standard Template Library (STL): C++ provides iterators as a core part of its STL. Different container types (e.g., vector, list, map) have their own iterator classes, allowing you to traverse their elements in a generic way. Iterators in C++ are more powerful than in Java, supporting various operations like increment, decrement, dereferencing, and comparison.

Specimens

15 implementations
Specimen 098.01 Dart View specimen ↗

The Iterator pattern provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation. It defines a standard interface for creating iterators and iterating over collections. This implementation uses Dart’s Iterable and Iterator classes. The Album class represents the aggregate, and the AlbumIterator class provides the iteration logic. Dart’s yield keyword makes creating iterators concise, and using an Iterable allows the Album class to be used with other Dart collection utilities easily. This embraces the functional aspects of Dart and utilizes its built-in iteration support.

// Define the iterator class
class AlbumIterator<T> implements Iterator<T> {
  final List<T> _data;
  int _currentIndex = 0;

  AlbumIterator(this._data);

  @override
  T get current => _data[_currentIndex];

  @override
  bool moveNext() {
    return _currentIndex < _data.length - 1;
  }
}

// Define the aggregate class (collection)
class Album<T> extends Iterable<T> {
  final List<T> _songs;

  Album(this._songs);

  @override
  Iterator<T> get iterator => AlbumIterator(_songs);

  void addSong(T song) {
    _songs.add(song);
  }
}

void main() {
  final album = Album<String>(['Song 1', 'Song 2', 'Song 3']);

  for (final song in album) {
    print(song);
  }

  final iterator = album.iterator;
  while (iterator.moveNext()) {
    print('Current song using iterator: ${iterator.current}');
  }
}