Why? To traverse through containers of objects.
There are different structures that let us store data in a structured format. A data structure should provide a way to step through their elements. We could come up with different solutions to access their elements sequentially. As a result, the interface of these data structures will be cluttered with operations for different traversers, and these operations would vary from type to type, which in turn makes them hard to use and maintain.
The iterator pattern addresses this problem. It provides a standard interface for traversing different types of aggregate objects. The iterator encapsulates the access and traversal logic in a separate type; thus, it removes these responsibilities from the data structure. So we don’t have to expose any of the details of the underlying data structure to iterate to its elements.
- Provides sequential access to the elements of an aggregate object.
- Encapsulates the operations for different traversals into a dedicated iterator type.
- Does not expose the internal structure of the traversed object.
- The iterator must keep track of the traversed elements.
- Calling its next method returns the current element, and it advances one step in the sequence.
- The iterator pattern is one of the most frequently used ones.
The iterator provides sequential access to the elements of a container without exposing its underlying details. It removes the operations for different traversers from the data structure itself.
Now, let’s take a look at the design of the iterator pattern. In most languages, all built-in collection types, the array, the set, and the dictionary, conform to a sequence interace/protocol. Our custom types must also adopt that interface if we need to iterate through their elements.
The Iterator Protocol provides a unified interface for accessing the items in the sequence one at a time. The next method advances to the next element in the sequence and returns it. Actually, we’re using the given type’s iterator whenever we rely on the for-in loop to iterate through its items.
Example: Stack, Queue, LinkedList, Arrays etc all have iterators.
Here’s a straightforward example of iterating through the elements of an array. The compiler generates the following code. It first creates an iterator by calling the array’s make Iterator method. Then, the while loop calls the iterator next. The loop exits when the next method does not return a valid value. Knowing these details is important when implementing custom iterators. Our next method implementation must also return nil or an invalid value to signal the end of the sequence; otherwise, the loop will continue forever.
Always consider the performance impact of your iterator implementation. The next method must not perform slow or computationally-intensive operations.