Are you dealing with a complex data structure?
Do you want to enhance your application’s performance by reducing the use of iterative code?
If yes, the Iterator design pattern is the solution to your problem.
The Iterator design pattern is a behavioral pattern that allows you to define an interface to access the elements of complex collections sequentially while hiding their representation from the client. It is widely used in the .NET programming environment.
This article will teach you about the Iterator design pattern and how to implement it to solve real programming problems.
What is the Iterator design pattern?
In object-oriented programming, when you have a recurring programming problem, you use design patterns to resolve it.
There are 23 design patterns, each of which plays a vital role in its place. The Gang of Four (GoF) divided these patterns into three categories:
The Iterator design pattern is a behavioral design pattern that allows you to access the elements of a complex data structure without exposing its underlying logic.
It implies that the Collections (that contain multiple objects), such as Lists, Arrays, and Dictionaries, should provide access to their elements without exposing their internal structure. To do so, you create an Iterator class that contains a reference to the corresponding object, and the Iterator traverses its aggregate to access its individual object.
When to use the Iterator design pattern?
As mentioned earlier, the Iterator pattern is extremely used in programming, especially when there is some complex data structure. However, here is a list of situations where you may need to implement the Iterator pattern:
- When you have a collection with a complex data structure, you want to hide its complexity from the clients and provide them with a convenient and secure way of accessing its elements.
- You can use an Iterator when you want to focus on the business logic and reduce code duplication. For example, instead of writing the traversal code multiple times, you can use a single Iterator to increase the maintainability and make it less bulky.
- You can use the Iterator pattern when your code needs to iterate through different data structures or their type is unknown beforehand.
How to implement the Iterator pattern in C# – Structural code
Before getting directly to the structural code, you must know about the participants of the Iterator pattern. It has 5 participants:
- Iterator: An interface that defines a way of accessing the elements of a collection.
- ConcreteIterator: A class that implements the Iterator interface and tracks the position of the iterator.
- Aggregate: An interface that provides a way to create the Iterator object.
- ConcreteAggegate: A class that implements the aggregate interface.
- Client: A class containing the object collection uses an Iterator method to access items from the aggregate sequentially.
Here is the UML diagram that depicts the structure of the Iterator pattern.
Now that you understand the participants of this pattern, let’s move to the structural code.
public interface Iterator
{
string FirstItem();
string NextItem();
bool Done();
string CurrentItem();
}
public class ConcreteIterator : Iterator
{
ConcreteAggregate _aggregate;
int _currentItemIndex = 0;
public ConcreteIterator(ConcreteAggregate C_aggregate)
{
this._aggregate = C_aggregate;
}
public string FirstItem()
{
return _aggregate[0];
}
public string NextItem()
{
string return_obj = null;
if (_currentItemIndex < _aggregate.Count - 1)
{
return_obj = _aggregate[++_currentItemIndex];
}
return return_obj;
}
public string CurrentItem()
{
return _aggregate[_currentItemIndex];
}
public bool Done()
{
return _currentItemIndex >= _aggregate.Count;
}
}
public interface Aggregate
{
Iterator Create();
}
public class ConcreteAggregate : Aggregate
{
List<string> _items = new List<string>();
public Iterator Create()
{
return new ConcreteIterator(this);
}
public int Count
{
get { return _items.Count; }
}
public string this[int index]
{
get { return _items[index]; }
set { _items.Insert(index, value); }
}
}
And the usage:
//Creating an aggregate
ConcreteAggregate aggregate = new ConcreteAggregate();
aggregate[0] = "Item 1";
aggregate[1] = "Item 2";
aggregate[2] = "Item 3";
aggregate[3] = "Item 4";
aggregate[4] = "Item 5";
// Creating the Iterator and assigning an aggregate to it
Iterator i = aggregate.Create();
Console.WriteLine("Iterating the collection:");
string currentItem = i.FirstItem();
while (currentItem != null)
{
Console.WriteLine(currentItem);
currentItem = i.NextItem();
}
Console.ReadKey();
provides the following output:
Iterating the collection:
Item 1
Item 2
Item 3
Item 4
Item 5
Benefits and drawbacks of the Iterator pattern
The Iterator pattern has the following benefits and drawbacks.
Benefits
- It follows the Single Responsibility Principle. It allows you to gather the bulky traversal code into separate classes. This also increases the code readability.
- It allows you to add classes and collections without changing the code, i.e., it satisfies the Open/Closed principle.
- Ensures clean code by providing the client with a simplified interface to interact with more complex structures.
- You can iterate through a collection in parallel, as each object contains its own iteration state.
- For the above reason, it allows you to stop, delay and continue any iteration when needed.
Drawbacks
- It sometimes affects the performance by consuming more memory than it will take to access the elements directly.
- It can overkill the functionality of an application when used for simple collections.
What are the related patterns to the Iterator design pattern?
The Iterator pattern is related to many design patterns based on the structure or functionality:
- You can use the Iterator to iterate through the tree structure in a Composite pattern.
- The Iterator and Memento patterns can work hand in hand to get the current iteration state and roll back if needed.
- The Factory Method and Iterator can be combined to let the collection subclasses return the corresponding types of iterators.
- You can perform certain operations on the complex collection’s elements using Visitor and Iterator patterns.
Conclusion
When you have complex data structures and need to iterate through them frequently, instead of repeating the traversal code, you can use the Iterator design pattern that provides a separate interface to iterate through the complex collections.
It is a widely used pattern in software engineering.
However, when combined with other design patterns, it can serve many more purposes.
This article has discussed the Iterator design pattern, its structural code, and a real-world example in C#. Hope that you benefit from this article!
If you want to learn more about design patterns, check the separate in-depth article about design patterns in C#.