Skip to main content

Understanding the Iterator Design Pattern in C#

 

The Iterator design pattern is a behavioral pattern that provides a way to access elements of a collection sequentially without exposing its underlying representation. This pattern is useful for traversing different data structures in a uniform way.

Understanding the Memento Design Pattern in C#

Let's consider a scenario where we have a Book class and a BookCollection class. We want to iterate over the BookCollection without exposing its internal details.

Example without Iterator Design Pattern

using System;
using System.Collections.Generic;

namespace WithoutIteratorPattern
{
    // Book class
    class Book
    {
        public string Title { get; private set; }

        public Book(string title)
        {
            Title = title;
        }
    }

    // Concrete collection
    class BookCollection
    {
        private readonly List<Book> _books = new List<Book>();

        public int Count => _books.Count;

        public Book this[int index] => _books[index];

        public void AddBook(Book book)
        {
            _books.Add(book);
        }

        public List<Book> GetBooks()
        {
            return _books;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            BookCollection collection = new BookCollection();
            collection.AddBook(new Book("Design Patterns"));
            collection.AddBook(new Book("Clean Code"));
            collection.AddBook(new Book("Refactoring"));

            List<Book> books = collection.GetBooks();

            Console.WriteLine("Iterating over collection:");
            for (int i = 0; i < books.Count; i++)
            {
                Console.WriteLine(books[i].Title);
            }
        }
    }
}

Problems in the Non-Pattern Approach

  1. Lack of Abstraction:The client code (in Program.Main) directly accesses the internal representation of the collection (List<Book>). This means any change in the collection type (e.g., from List<Book> to another collection type) requires changes in the client code.

  2. Limited Flexibility:If different iteration behaviors are needed (e.g., reverse iteration, skipping certain elements), the client code must be modified, making it less flexible and harder to maintain.

  3. Code Duplication:Each time we need to iterate over the collection, we would write similar looping code, leading to code duplication and potential inconsistencies.

  4. Encapsulation Violation:Exposing the internal list of books through GetBooks() method breaks encapsulation, making it possible for the client code to modify the collection directly, which can lead to unexpected behaviors.
How Iterator Pattern Solves These Problems
  1. Encapsulation:The Iterator Pattern encapsulates the iteration logic inside the iterator. The client code interacts with the collection through the iterator interface, without knowing or depending on the underlying collection structure.

  2. Single Responsibility:The collection class (BookCollection) is responsible for managing the collection of books, and the iterator class (BookIterator) is responsible for the iteration logic. This separation of concerns makes the code more modular and easier to maintain.

  3. Flexibility:Different iteration behaviors can be implemented by creating different iterators. For example, if you need a reverse iterator, you can create a ReverseBookIterator without changing the client code.

  4. Consistency:The iteration logic is centralized in the iterator, reducing code duplication and ensuring consistent behavior across different parts of the application.

Revisited Code with Iterator Pattern

Here is how we can implement this pattern:

using System;
using System.Collections;
using System.Collections.Generic;

namespace IteratorPattern
{
    // Book class
    class Book
    {
        public string Title { get; private set; }

        public Book(string title)
        {
            Title = title;
        }
    }

    // Iterator interface
    interface IIterator<T>
    {
        T Current { get; }
        bool MoveNext();
        void Reset();
    }

    // Concrete iterator
    class BookIterator : IIterator<Book>
    {
        private readonly BookCollection _collection;
        private int _currentIndex = -1;

        public BookIterator(BookCollection collection)
        {
            _collection = collection;
        }

        public Book Current => _collection[_currentIndex];

        public bool MoveNext()
        {
            _currentIndex++;
            return _currentIndex < _collection.Count;
        }

        public void Reset()
        {
            _currentIndex = -1;
        }
    }

    // Collection interface
    interface IBookCollection
    {
        IIterator<Book> CreateIterator();
        int Count { get; }
        Book this[int index] { get; }
    }

    // Concrete collection
    class BookCollection : IBookCollection
    {
        private readonly List<Book> _books = new List<Book>();

        public int Count => _books.Count;

        public Book this[int index] => _books[index];

        public void AddBook(Book book)
        {
            _books.Add(book);
        }

        public IIterator<Book> CreateIterator()
        {
            return new BookIterator(this);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            BookCollection collection = new BookCollection();
            collection.AddBook(new Book("Design Patterns"));
            collection.AddBook(new Book("Clean Code"));
            collection.AddBook(new Book("Refactoring"));

            IIterator<Book> iterator = collection.CreateIterator();

            Console.WriteLine("Iterating over collection:");
            while (iterator.MoveNext())
            {
                Console.WriteLine(iterator.Current.Title);
            }
        }
    }
}

Why Can't We Use Other Design Patterns Instead?

  • Composite Pattern: The Composite pattern is used for treating individual objects and compositions of objects uniformly. It does not provide a mechanism for traversing a collection.
  • Visitor Pattern: The Visitor pattern is used for performing operations on elements of an object structure without changing the classes of the elements. It is more suitable for operations that need to be applied across a collection rather than simple traversal.
  • Command Pattern: The Command pattern encapsulates a request as an object, allowing for parameterization and queuing of requests. It is not designed for accessing elements of a collection sequentially.

Steps to Identify Use Cases for the Iterator Pattern

  1. Collection Traversal: Identify scenarios where you need to traverse elements of a collection sequentially.
  2. Encapsulation Requirement: Ensure that the internal representation of the collection should be hidden from the client.
  3. Uniform Access: Consider the Iterator pattern when you need a uniform way to traverse different types of collections.
  4. Multiple Traversals: Use the Iterator pattern to enable multiple independent traversals of a collection.

By following these steps and implementing the Iterator pattern, you can achieve encapsulated and uniform traversal of collections, improving flexibility and separation of concerns in your system.

Comments

Popular posts from this blog

Optional Parameters in C# — Writing Flexible and Clean Methods

Hello, .NET developers! 👋 How often have you created multiple method overloads just to handle slightly different cases? Maybe one method accepts two parameters, another three, and one more adds a flag for debugging? That’s a lot of code duplication for something that can be solved beautifully with optional parameters . Optional parameters in C# let you define default values for method arguments. When a caller doesn’t pass a value, the compiler automatically substitutes the default. This feature helps keep your APIs simple, readable, and maintainable. 🎥 Explore more on YouTube : DotNet Full Stack Dev Understanding Optional Parameters Optional parameters are defined by assigning default values in the method signature. When calling the method, you can omit those parameters if you’re okay with the defaults. Example public class Logger { public void Log(string message, string level = "INFO", bool writeToFile = false) ...

.NET 10: Your Ultimate Guide to the Coolest New Features (with Real-World Goodies!)

 Hey .NET warriors! 🤓 Are you ready to explore the latest and greatest features that .NET 10 and C# 14 bring to the table? Whether you're a seasoned developer or just starting out, this guide will show you how .NET 10 makes your apps faster, safer, and more productive — with real-world examples to boot! So grab your coffee ☕️ and let’s dive into the awesome . 💪 1️⃣ JIT Compiler Superpowers — Lightning-Fast Apps .NET 10 is all about speed . The Just-In-Time (JIT) compiler has been turbocharged with: Stack Allocation for Small Arrays 🗂️ Think fewer heap allocations, less garbage collection, and blazing-fast performance . Better Code Layout 🔥 Hot code paths are now smarter, meaning faster method calls and fewer CPU cache misses. 💡 Why you care: Your APIs, desktop apps, and services now respond quicker — giving users a snappy experience . 2️⃣ Say Hello to C# 14 — More Power in Your Syntax .NET 10 ships with C# 14 , and it’s packed with developer goodies: Field-Bac...

Implementing and Integrating RabbitMQ in .NET Core Application: Shopping Cart and Order API

RabbitMQ is a robust message broker that enables communication between services in a decoupled, reliable manner. In this guide, we’ll implement RabbitMQ in a .NET Core application to connect two microservices: Shopping Cart API (Producer) and Order API (Consumer). 1. Prerequisites Install RabbitMQ locally or on a server. Default Management UI: http://localhost:15672 Default Credentials: guest/guest Install the RabbitMQ.Client package for .NET: dotnet add package RabbitMQ.Client 2. Architecture Overview Shopping Cart API (Producer): Sends a message when a user places an order. RabbitMQ : Acts as the broker to hold the message. Order API (Consumer): Receives the message and processes the order. 3. RabbitMQ Producer: Shopping Cart API Step 1: Install RabbitMQ.Client Ensure the RabbitMQ client library is installed: dotnet add package RabbitMQ.Client Step 2: Create the Producer Service Add a RabbitMQProducer class to send messages. RabbitMQProducer.cs : using RabbitMQ.Client; usin...