Iterator

Provide a way to accessthe elements of an aggregate object sequentially without exposing its underlying representation.

Introduction

  • Iterator, also known as Cursor, is a design pattern belonging to the Behavioral Pattern group.

  • The Iterator is designed to allow processing of many different types of sets by accessing the elements of the set with the same method, in the same predetermined manner, without having to understand the internal details of the collection. these sets.

  • In other words, an Iterator is designed to allow processing of many different types of sets by accessing the elements of the set with the same method, in the same predetermined manner, without having to understand clearly about the set elements. internal details of these assemblies.

  • This design idea is one of the techniques known as the “single responsibility principle (SRP)” – a class has only one job to do. Think that a collection maintains elements, an iterator provides a way of working with those elements. That's also why Iterators can work in different collections.

Problem

Collections are one of the most used data types in programming. Nonetheless, a collection is just a container for a group of objects.

Various types of collections
Various types of collections.

Most collections store their elements in simple lists. However, some of them are based on stacks, trees, graphs and other complex data structures.

But no matter how a collection is structured, it must provide some way of accessing its elements so that other code can use these elements. There should be a way to go through each element of the collection without accessing the same elements over and over.

This may sound like an easy job if you have a collection based on a list. You just loop over all of the elements. But how do you sequentially traverse elements of a complex data structure, such as a tree? For example, one day you might be just fine with depth-first traversal of a tree. Yet the next day you might require breadth-first traversal. And the next week, you might need something else, like random access to the tree elements.

Adding more and more traversal algorithms to the collection gradually blurs its primary responsibility, which is efficient data storage. Additionally, some algorithms might be tailored for a specific application, so including them into a generic collection class would be weird.

On the other hand, the client code that’s supposed to work with various collections may not even care how they store their elements. However, since collections all provide different ways of accessing their elements, you have no option other than to couple your code to the specific collection classes.

Solution

The main idea of the Iterator pattern is to extract the traversal behavior of a collection into a separate object called an iterator.

In addition to implementing the algorithm itself, an iterator object encapsulates all of the traversal details, such as the current position and how many elements are left till the end. Because of this, several iterators can go through the same collection at the same time, independently of each other.

Usually, iterators provide one primary method for fetching elements of the collection. The client can keep running this method until it doesn’t return anything, which means that the iterator has traversed all of the elements.

All iterators must implement the same interface. This makes the client code compatible with any collection type or any traversal algorithm as long as there’s a proper iterator. If you need a special way to traverse a collection, you just create a new iterator class, without having to change the collection or the client.

Real-World Analogy

You plan to visit Rome for a few days and visit all of its main sights and attractions. But once there, you could waste a lot of time walking in circles, unable to find even the Colosseum.

On the other hand, you could buy a virtual guide app for your smartphone and use it for navigation. It’s smart and inexpensive, and you could be staying at some interesting places for as long as you want.

A third alternative is that you could spend some of the trip’s budget and hire a local guide who knows the city like the back of his hand. The guide would be able to tailor the tour to your likings, show you every attraction and tell a lot of exciting stories. That’ll be even more fun; but, alas, more expensive, too.

All of these options—the random directions born in your head, the smartphone navigator or the human guide—act as iterators over the vast collection of sights and attractions located in Rome.

Structure

Components in the model:

  • Iterator : is an interface or abstract class, declaring the necessary operations to compare a collection: fetch the next element, retrieve the current position, restart iteration, etc.

  • Concrete Iterator : implements Iterator methods, keeps index when traversing elements. Allows several iterators to traverse the same collection independently of each other.

  • Collection Interface : declares one or more methods to receive Iterators compatible with the collection. Note that the return type of the methods must be declared as an Iterator Interface so that concrete collections can return Iterator types.

  • Concrete Collections: returns new instances of a specific Concrete Iterator class each time a client requests it.

  • Client : the object uses the Iterator Pattern, it requests an iterator from a collection object to iterate over the elements it holds. Iterator methods are used to retrieve elements from the collection in an appropriate order.

Advantages & disadvantages

Advantage

  • Ensure the Single responsibility principle (SRP): we can separate the implementation of collection methods and the iterator of each individual class.

  • Ensuring the Open/Closed Principle (OCP): we can implement new collection types and new iterators, then move them into existing code without violating any principles.

  • We can access the same collection in parallel because each iterator object contains its own state.

  • In some cases, you can delay a repetition and continue it as needed.

Disadvantage

  • Using an iterator can be less efficient than iterating over the collection's elements directly.

  • It may not be necessary if the application only works with simple collections.

When to use it

Below we can list some cases where we should consider using the Iterator pattern:

  • Need to access the contents of an object in a collection without knowing its internal settings: Use the Iterator pattern when your collection has a complex data structure, but you want to hide its overall complexity with clients (for convenience or security). => Iterators encapsulate the details of working with complex data structures, providing the client with some simulated methods to access collection elements.

  • Use templates to reduce source code duplication: the code of iterative algorithms is often very complex and tends to be cumbersome. =>When placed within the business logic of an application, it can overshadow the role of the original source code and make it more difficult to maintain. By moving that source code to designated Iterators, you can make your code neater and cleaner.

  • Use Iterator when you want a single interface to iterate over the elements of a collection, your code can follow different data structures, or when these types of string structures are not known in advance. => The pattern provides some interfaces common to both collections and iterators. Your code will now use Interfaces, and it will still work with various collections and iterators to implement these interfaces.

Applicability

  • Use the Iterator pattern when your collection has a complex data structure under the hood, but you want to hide its complexity from clients (either for convenience or security reasons).

    • The iterator encapsulates the details of working with a complex data structure, providing the client with several simple methods of accessing the collection elements. While this approach is very convenient for the client, it also protects the collection from careless or malicious actions which the client would be able to perform if working with the collection directly.

  • Use the pattern to reduce duplication of the traversal code across your app.

    • The code of non-trivial iteration algorithms tends to be very bulky. When placed within the business logic of an app, it may blur the responsibility of the original code and make it less maintainable. Moving the traversal code to designated iterators can help you make the code of the application more lean and clean.

  • Use the Iterator when you want your code to be able to traverse different data structures or when types of these structures are unknown beforehand.

    • The pattern provides a couple of generic interfaces for both collections and iterators. Given that your code now uses these interfaces, it’ll still work if you pass it various kinds of collections and iterators that implement these interfaces.

How to Implement

  1. Declare the iterator interface. At the very least, it must have a method for fetching the next element from a collection. But for the sake of convenience you can add a couple of other methods, such as fetching the previous element, tracking the current position, and checking the end of the iteration.

  2. Declare the collection interface and describe a method for fetching iterators. The return type should be equal to that of the iterator interface. You may declare similar methods if you plan to have several distinct groups of iterators.

  3. Implement concrete iterator classes for the collections that you want to be traversable with iterators. An iterator object must be linked with a single collection instance. Usually, this link is established via the iterator’s constructor.

  4. Implement the collection interface in your collection classes. The main idea is to provide the client with a shortcut for creating iterators, tailored for a particular collection class. The collection object must pass itself to the iterator’s constructor to establish a link between them.

  5. Go over the client code to replace all of the collection traversal code with the use of iterators. The client fetches a new iterator object each time it needs to iterate over the collection elements.

Relations with Other Patterns

  • You can use Iterators to traverse Composite trees.

  • You can use Factory Method along with Iterator to let collection subclasses return different types of iterators that are compatible with the collections.

  • You can use Memento along with Iterator to capture the current iteration state and roll it back if necessary.

  • You can use Visitor along with Iterator to traverse a complex data structure and execute some operations over its elements, even if they all have different classes.

Last updated