Iterator Design Pattern in C# with Example

1. Definition

The Iterator Design Pattern provides a way to access the elements of an aggregate object (like a collection) sequentially without exposing its underlying representation. The pattern decouples collection objects and iterators.

2. Problem Statement

Imagine you have different collection objects (like a list, set, or tree), and you want to provide a standardized way to traverse these collections without revealing their internal structures. Additionally, you might want to provide multiple ways to traverse a single collection.

3. Solution

The Iterator pattern achieves this by using a separate object, the iterator, which encapsulates the details of accessing and traversing the collection elements.

4. Real-World Use Cases

1. Navigating through a playlist in a music player.

2. Iterating over different types of custom collections.

3. Traversing complex data structures, like trees or graphs.

5. Implementation Steps

1. Define an Iterator interface that declares methods for traversing the collection.

2. Implement ConcreteIterators that implement the Iterator interface for specific collections.

3. Define an Aggregate interface that declares a method to obtain an iterator.

4. Implement ConcreteAggregates that contain collections and return ConcreteIterators.

6. Implementation in C#

// Step 1: Iterator Interface
public interface IIterator<T>
{
    bool HasNext();
    T Next();
}

// Step 2: ConcreteIterator
public class ListIterator<T> : IIterator<T>
{
    private readonly List<T> _items;
    private int _index;

    public ListIterator(List<T> items)
    {
        _items = items;
        _index = 0;
    }

    public bool HasNext()
    {
        return _index < _items.Count;
    }

    public T Next()
    {
        return _items[_index++];
    }
}

// Step 3: Aggregate Interface
public interface IAggregate<T>
{
    IIterator<T> GetIterator();
}

// Step 4: ConcreteAggregate
public class ConcreteList<T> : IAggregate<T>
{
    private List<T> _items;

    public ConcreteList()
    {
        _items = new List<T>();
    }

    public void Add(T item)
    {
        _items.Add(item);
    }

    public IIterator<T> GetIterator()
    {
        return new ListIterator<T>(_items);
    }
}

public class Program
{
    public static void Main()
    {
        // Client code
        var itemsList = new ConcreteList<string>();
        itemsList.Add("Item1");
        itemsList.Add("Item2");
        itemsList.Add("Item3");

        var iterator = itemsList.GetIterator();

        while (iterator.HasNext())
        {
            Console.WriteLine(iterator.Next());
        }
    }
}

Output:

Item1
Item2
Item3

Explanation:

In the provided example, we have defined an iterator (ListIterator) to iterate over a list (ConcreteList). 

The client code can now iterate over the list using the iterator, without knowing the internal representation of the ConcreteList.

7. When to use?

1. Use the Iterator pattern when you want to provide multiple ways to traverse a collection, without exposing its internal structure.

2. When you want to decouple collection classes from algorithms that operate on them.

3. When you want to introduce new types of collections without breaking existing code.


Comments