Design Patterns - Iterator

24 juni 2022 om 10:00 by ParTech Media - Post a comment

In our previous blog of the design pattern series, we had discussed the interpreter design pattern, which falls under the category of behavioral design patterns. Today, we will explore another design pattern from the same category called Iterator Design Pattern.

Table of contents

  1. Introduction to Iterator Design Pattern
  2. Implementation of Iterator Design Pattern
  3. Conclusion

Introduction to Iterator Design Pattern

The Iterator pattern provides a way to go through and access elements of a collection sequentially without exposing its structure. Iterator design patterns are usually used in the menu and taskbar items of day-to-day developer applications such as IDEs and Editors. In the .Net world, First() and Count() LINQ methods of objects are achieved with the help of the iterator design pattern.

Implementation of Iterator design pattern

The Iterator design pattern mainly consists of:

  • Aggregate
  • Concrete Aggregate
  • Iterator
  • Concrete Iterator
  • Client

Here is a template that we can use to show the implementation of the iterator design pattern -

using System;
using System.Collections;

namespace PARTECH_Iterator
{
    internal class Program
    {
        static void Main(string[] args)
        {
            ConcreteAggregate concreteAggregate = new ConcreteAggregate();
            concreteAggregate.Add("One");
            concreteAggregate.Add("Two");
            concreteAggregate.Add("Three");

            Iterator iterator = concreteAggregate.CreateIterator();

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

            Console.ReadLine();
        }
    }

    public interface Iterator
    {
        object Current { get; }
        bool Next();
    }

    public interface Aggregate
    {
        Iterator CreateIterator();
    }

    public class ConcreteAggregate: Aggregate
    {
        private ArrayList items = new ArrayList();

        public Iterator CreateIterator()
        {
            return new ConcreteIterator(this);
        }

        public object this[int index]
        {
            get { return items[index]; }
        }

        public int Count
        { 
            get { return items.Count; } 
        }

        public void Add(object obj)
        {
            items.Add(obj);
        }
    }

    public class ConcreteIterator: Iterator
    {
        private ConcreteAggregate _aggregate;
        int index;

        public ConcreteIterator(ConcreteAggregate aggregate)
        {
            _aggregate = aggregate;
            index = -1;
        }

        public bool Next()
        {
            index++;
            return index < _aggregate.Count;
        }

        public object Current
        {
            get
            {
                if (index < _aggregate.Count)
                    return _aggregate[index];
                else 
                    return null;
            }
        }
    } 
}

Here, The iterator is the interface that provides a signature to perform an operation on the collection elements.

ConcreteIterator is the class that implements the Iterator interface and facilitates implementation to run through the collection elements sequentially.

Aggregate is the interface that provides the signature of the method, which helps in creating an Iterator.

Concrete Aggregate is the class that implements Aggregate and implements the logic to create an iterator. It also implements the methods which can be performed on the collection, such as count, add, remove, and so on.

Finally, the client (Main method) has the code to create an object concrete Aggregate, and elements get added to it. However, the Add method is a wrapper for the add logic of the collection. An iterator is created for the collection, which helps in traversing through the collection elements (until the last element) without exposing the logic used to traverse through. Finally, for easier understanding, in the main method, we have traversed through each element and logged them for easier understanding.

On executing the above code, the below output gets generated.

Now that we have understood the basic template to implement the Iterator design pattern let us implement the same through a practical scenario.

As the Iterator design pattern is all about traversing through a collection sequentially, it has found profound use in various applications. For instance, it can be implemented as a framework code where it has the logic to perform the operations in a collection such as create, add, remove, find specific, and so on. This, in turn, can be referred to in many other codes for accessing the collection and traversing sequentially. It also can be implemented in places where order matters (such as processing requests).

Now, let us consider a simple student collection and understand how it can be traversed using the iterator design pattern.

using System;
using System.Collections.Generic;

namespace PARTECH_Iterator
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var collection = new Collection();

            collection.Add(new Student("Student 1"));
            collection.Add(new Student("Student 2"));
            collection.Add(new Student("Student 3"));
            collection.Add(new Student("Student 4"));
            collection.Add(new Student("Student 5"));
            collection.Add(new Student("Student 6"));

            var iterator = collection.GetEnumerator();

            for (Student item = iterator.First();
                !iterator.Processed; item = iterator.Next())
            {
                Console.WriteLine(item.Name);
            }

            Console.ReadLine();
        }
    }

    public class Student
    {
        public string Name { get; set; }
        public Student(string name)
        {
            Name = name;
        }
    }

    public interface IEnumerable
    {
        Iterator GetEnumerator();
    }

    public interface Iterator
    {
        Student First();
        Student Next();
        Student Current();
        bool Processed { get; }
    }

    public class Collection : IEnumerable
    {
        List<Student> students = new List<Student> ();
        public Iterator GetEnumerator()
        {
            return new Enumerator(this);
        }

        public Student this[int index]
        {
            get { return students[index]; }
        }

        public int Count
        {
            get { return students.Count; }
        }

        public void Add(Student obj)
        {
            students.Add(obj);
        }
    }

    public class Enumerator : Iterator
    {
        Collection collection;
        int current = 0;
        int step = 1;

        public Enumerator(Collection collection)
        {
            this.collection = collection;
        }

        public Student First()
        {
            current = 0;
            return collection[current];
        }
        
        public Student Next()
        {
            current += step;
            if (current < collection.Count)
                return collection[current];
            else
                return null;
        }
        
        public int Step
        {
            get { return step; }
            set { step = value; }
        }
        
        public Student Current()
        {
            return collection[current];
        }

        public bool Processed
        {
            get { return current >= collection.Count; }
        }
    }
}

In the above example, IEnumerable is the Aggregate; Collection is the Concrete Aggregate; Iterator is the iterator; Enumerator is the concrete Iterator, and the Main method is the client. Apart from the template class and interface of the iterator design pattern, we have a custom class, Student. Here the collection is maintained for the Student data.

IEnumerable interface provides the signature to create an iterator. Iterator provides a signature to traverse through the collection, such as First, Next, Current, and so on. In addition to these, a step operation is provided where a user can define the step size. For instance, a scenario where every element needs to be processed sequentially or a few elements can be skipped while processing. The step value is defaulted to 1, which can be modified as and when needed. On executing the above code, the below output gets printed.

Conclusion

And that’s what an Iterator Behavioral design pattern is all about. In this post, we have also seen how to implement it in a real-world scenario. Stay tuned for the next blog on the Design Patterns series.

Nieuwste