Real-Time Examples of Iterator Design Pattern in C#

Real-Time Examples of Iterator Design Pattern in C#

In this article, I will discuss the Real-Time Examples of Iterator Design Pattern in C#. Please read our previous article discussing the basic concepts of the Iterator Design Pattern in C#. In this article, we will implement the following real-time examples using the Iterator Design Pattern in C#.

  1. Browsing Music Playlist
  2. Bookshelf
  3. Radio Station
  4. Navigating a Collection of TV Channels
  5. University Course Registration System
  6. Social Media Feed
  7. Restaurant Menu
What is the Iterator Design Pattern?

The Iterator pattern is a design pattern that provides a way to access the elements of a collection object sequentially without exposing its underlying representation. It involves two primary types:

  • Iterator: Defines an interface for accessing and traversing elements.
  • Aggregate: An interface for creating an Iterator object.
Real-Time Examples of Iterator Design Pattern in C#: Browsing Music Playlist

A real-time example of the Iterator pattern can be browsing a music playlist where songs are stored in a particular order, and the user can sequentially traverse through the playlist. Let’s implement this using the Iterator Design Pattern in C#:

In this example, a Playlist is an aggregate of songs. The PlaylistIterator provides a way to iterate through the playlist sequentially. This simulates the real-world scenario where you can traverse a playlist one song at a time.

using System;
using System.Collections.Generic;

namespace IteratorDesignPattern
{
    //Iterator and Aggregate Interfaces
    public interface IIterator<T>
    {
        bool HasNext();
        T Next();
    }

    public interface IAggregate<T>
    {
        IIterator<T> CreateIterator();
    }

    //Concrete Aggregate - Playlist
    public class Playlist : IAggregate<Song>
    {
        private List<Song> _songs = new List<Song>();

        public void AddSong(Song song)
        {
            _songs.Add(song);
        }

        public IIterator<Song> CreateIterator()
        {
            return new PlaylistIterator(this);
        }

        public int Count => _songs.Count;

        public Song this[int index] => _songs[index];
    }

    //Concrete Song Class
    public class Song
    {
        public string Title { get; set; }
        public string Artist { get; set; }

        public Song(string title, string artist)
        {
            Title = title;
            Artist = artist;
        }

        public override string ToString() => $"{Title} by {Artist}";
    }

    //Concrete Iterator - PlaylistIterator
    public class PlaylistIterator : IIterator<Song>
    {
        private readonly Playlist _playlist;
        private int _index = 0;

        public PlaylistIterator(Playlist playlist)
        {
            _playlist = playlist;
        }

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

        public Song Next()
        {
            return _playlist[_index++];
        }
    }

    //Client Code
    public class Program
    {
        static void Main()
        {
            var myPlaylist = new Playlist();

            myPlaylist.AddSong(new Song("Title 1", "Artist 1"));
            myPlaylist.AddSong(new Song("Title 2", "Artist 2"));
            myPlaylist.AddSong(new Song("Title 3", "Artist 3"));

            IIterator<Song> iterator = myPlaylist.CreateIterator();
            while (iterator.HasNext())
            {
                Console.WriteLine(iterator.Next());
            }
            Console.ReadLine();
        }
    }
}
Output:

Real-Time Examples of Iterator Design Pattern in C#: Browsing Music Playlist

Real-Time Example of Iterator Design Pattern in C#: Bookshelf

Imagine you have a bookshelf, and you want to provide a way for readers to browse books on the shelf without knowing how the books are organized.

using System;
using System.Collections.Generic;

namespace IteratorDesignPattern
{
    //Define the Book
    public class Book
    {
        public string Title { get; private set; }
        public string Author { get; private set; }

        public Book(string title, string author)
        {
            Title = title;
            Author = author;
        }
    }

    //Define the Iterator and Aggregate interfaces
    public interface IIterator<T>
    {
        bool HasNext();
        T Next();
    }

    public interface IAggregate<T>
    {
        IIterator<T> CreateIterator();
    }

    //Implement a concrete aggregate, Bookshelf, and its iterator
    public class Bookshelf : IAggregate<Book>
    {
        private List<Book> _books = new List<Book>();

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

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

        private class BookshelfIterator : IIterator<Book>
        {
            private Bookshelf _bookshelf;
            private int _currentIndex = 0;

            public BookshelfIterator(Bookshelf bookshelf)
            {
                _bookshelf = bookshelf;
            }

            public bool HasNext()
            {
                return _currentIndex < _bookshelf._books.Count;
            }

            public Book Next()
            {
                return _bookshelf._books[_currentIndex++];
            }
        }
    }
    
    //Client Code
    public class Program
    {
        static void Main()
        {
            var shelf = new Bookshelf();
            shelf.Add(new Book("C#.NET", "Pranaya Rout"));
            shelf.Add(new Book("ASP.NET Core", "Prateek Sahoo"));
            shelf.Add(new Book("Entity Framework", "Hina Sharma"));

            var iterator = shelf.CreateIterator();
            while (iterator.HasNext())
            {
                var book = iterator.Next();
                Console.WriteLine($"Title: {book.Title}, Author: {book.Author}");
            }

            Console.ReadKey();
        }
    }
}
Explanation:
  • Book: Represents individual books with a title and an author.
  • IIterator and IAggregate: Core interfaces for the Iterator pattern.
  • Bookshelf: Represents the collection of books. It provides methods to add books and retrieve an iterator.
  • BookshelfIterator: Concrete iterator for the Bookshelf class. It provides mechanisms to iterate through the books on the shelf.
  • Client Code: Demonstrates adding books to the bookshelf and iterating through them using the provided iterator.
Output:

Real-Time Example of Iterator Design Pattern in C#: Bookshelf

By employing the Iterator pattern, the internal organization and storage mechanism of the bookshelf are abstracted away. Users or readers can browse the books without understanding how they are stored. The structure can be changed internally (e.g., from a list to an array or any other collection) without affecting how clients interact.

Real-Time Example of Iterator Design Pattern in C#: Radio Station

Imagine you have a Radio Station with multiple channels and want a radio device that lets the user navigate through these channels with forward and backward buttons. You can implement this using the Iterator Design Pattern in C#.

using System;
using System.Collections.Generic;

namespace IteratorDesignPattern
{
    //Define Channel and RadioStation
    public class Channel
    {
        public double Frequency { get; private set; }
        public string Name { get; private set; }

        public Channel(double frequency, string name)
        {
            Frequency = frequency;
            Name = name;
        }
    }

    public class RadioStation
    {
        private List<Channel> _channels = new List<Channel>();

        public void AddChannel(Channel channel)
        {
            _channels.Add(channel);
        }

        public IIterator<Channel> GetIterator()
        {
            return new RadioIterator(_channels);
        }
    }

    //Define Iterator and Concrete Iterator
    public interface IIterator<T>
    {
        bool HasNext();
        T Next();
        bool HasPrevious();
        T Previous();
    }

    public class RadioIterator : IIterator<Channel>
    {
        private List<Channel> _channels;
        private int _position = 0;

        public RadioIterator(List<Channel> channels)
        {
            _channels = channels;
        }

        public bool HasNext()
        {
            return _position < _channels.Count;
        }

        public Channel Next()
        {
            if (HasNext())
            {
                return _channels[_position++];
            }
            return null;
        }

        public bool HasPrevious()
        {
            return _position > 0;
        }

        public Channel Previous()
        {
            if (HasPrevious())
            {
                return _channels[--_position];
            }
            return null;
        }
    }
    
    //Client Code
    public class Program
    {
        static void Main()
        {
            RadioStation myStation = new RadioStation();
            myStation.AddChannel(new Channel(89.1, "Classic Rock"));
            myStation.AddChannel(new Channel(102.2, "Pop Hits"));
            myStation.AddChannel(new Channel(98.7, "News Channel"));

            IIterator<Channel> iterator = myStation.GetIterator();

            Console.WriteLine("Forward:");
            while (iterator.HasNext())
            {
                Console.WriteLine(iterator.Next().Name);
            }

            Console.WriteLine("Backward:");
            while (iterator.HasPrevious())
            {
                Console.WriteLine(iterator.Previous().Name);
            }

            Console.ReadKey();
        }
    }
}
Explanation:
  • Users can easily navigate between channels using a simple interface without worrying about the underlying implementation.
  • The RadioStation’s internal structure (in this case, a list) is encapsulated and is not exposed to clients.
  • This design provides flexibility; if the channels were stored in a different data structure in the future (e.g., an array or tree), we’d only need to modify the iterator, not the client code.
Output:

Real-Time Example of Iterator Design Pattern in C#: Radio Station

This radio station example gives a real-life perspective on how the Iterator Design Pattern can be useful in managing collections and providing an interface for navigation.

Real-Time Example of Iterator Design Pattern in C#: Navigating a Collection of TV Channels

Consider a television remote control. You can browse channels one by one without knowing the specifics of how channels are stored, organized, or transmitted. We can design a simple “Channel Collection” and allow the user to iterate using an iterator.

using System;
using System.Collections.Generic;

namespace IteratorDesignPattern
{
    //Channel and ChannelType
    public enum ChannelType
    {
        News,
        Sports,
        Movies,
        Music
    }

    public class Channel
    {
        public double Frequency { get; private set; }
        public ChannelType Type { get; private set; }

        public Channel(double frequency, ChannelType type)
        {
            Frequency = frequency;
            Type = type;
        }
    }

    //Iterator Interface
    public interface IIterator
    {
        bool HasNext();
        Channel Next();
    }

    //Collection Interface
    public interface IChannelCollection
    {
        void AddChannel(Channel c);
        void RemoveChannel(Channel c);
        IIterator GetIterator();
    }

    //Concrete Collection and Iterator
    public class ChannelCollection : IChannelCollection
    {
        private List<Channel> _channelsList;

        public ChannelCollection()
        {
            _channelsList = new List<Channel>();
        }

        public void AddChannel(Channel c)
        {
            _channelsList.Add(c);
        }

        public void RemoveChannel(Channel c)
        {
            _channelsList.Remove(c);
        }

        public IIterator GetIterator()
        {
            return new ChannelIterator(_channelsList);
        }

        private class ChannelIterator : IIterator
        {
            private readonly List<Channel> _channels;
            private int _position;

            public ChannelIterator(List<Channel> channels)
            {
                _channels = channels;
            }

            public bool HasNext()
            {
                return _position < _channels.Count;
            }

            public Channel Next()
            {
                Channel channel = _channels[_position];
                _position++;
                return channel;
            }
        }
    }
    
    //Client Code
    public class Program
    {
        static void Main()
        {
            var channelsCollection = new ChannelCollection();
            channelsCollection.AddChannel(new Channel(98.5, ChannelType.News));
            channelsCollection.AddChannel(new Channel(99.2, ChannelType.Music));
            channelsCollection.AddChannel(new Channel(100.7, ChannelType.Sports));

            var iterator = channelsCollection.GetIterator();

            while (iterator.HasNext())
            {
                Channel c = iterator.Next();
                Console.WriteLine($"Frequency: {c.Frequency}, Type: {c.Type}");
            }

            Console.ReadLine();
        }
    }
}

When you execute the above code, the Iterator pattern will allow you to navigate the collection of channels seamlessly. This provides a clean separation between the collection (aggregate) and the logic to traverse it (iterator). This abstraction helps maintain and extend code without altering existing functionality in real-world scenarios.

Real-Time Example of Iterator Design Pattern in C#: Navigating a Collection of TV Channels

Real-Time Example of Iterator Design Pattern in C#: University Course Registration System

In a university, there are various courses. Each course has multiple students registered. An administrator wants to view all students across courses without checking each course individually.

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

namespace IteratorDesignPattern
{
    //Define the Student
    public class Student
    {
        public string Name { get; private set; }
        public string ID { get; private set; }

        public Student(string name, string id)
        {
            Name = name;
            ID = id;
        }
    }

    //Define the Iterator and Aggregate interfaces
    public interface IIterator<T>
    {
        bool HasNext();
        T Next();
    }

    public interface IAggregate<T>
    {
        IIterator<T> CreateIterator();
    }

    //Implement a concrete aggregate, Course, and the collection University:
    public class Course : IAggregate<Student>
    {
        private List<Student> _students = new List<Student>();

        public void RegisterStudent(Student student)
        {
            _students.Add(student);
        }

        public IIterator<Student> CreateIterator()
        {
            return new CourseIterator(this);
        }

        private class CourseIterator : IIterator<Student>
        {
            private Course _course;
            private int _currentIndex = 0;

            public CourseIterator(Course course)
            {
                _course = course;
            }

            public bool HasNext()
            {
                return _currentIndex < _course._students.Count;
            }

            public Student Next()
            {
                return _course._students[_currentIndex++];
            }
        }
    }

    public class University : IAggregate<Student>
    {
        private List<Course> _courses = new List<Course>();

        public void AddCourse(Course course)
        {
            _courses.Add(course);
        }

        public IIterator<Student> CreateIterator()
        {
            return new UniversityIterator(this);
        }

        private class UniversityIterator : IIterator<Student>
        {
            private University _university;
            private int _courseIndex = 0;
            private IIterator<Student> _currentCourseIterator;

            public UniversityIterator(University university)
            {
                _university = university;
                if (_university._courses.Any())
                {
                    _currentCourseIterator = _university._courses[_courseIndex].CreateIterator();
                }
            }

            public bool HasNext()
            {
                if (_currentCourseIterator == null) return false;

                if (_currentCourseIterator.HasNext()) return true;

                // Move to the next course
                if (_courseIndex < _university._courses.Count - 1)
                {
                    _currentCourseIterator = _university._courses[++_courseIndex].CreateIterator();
                    return _currentCourseIterator.HasNext();
                }

                return false;
            }

            public Student Next()
            {
                if (_currentCourseIterator != null && _currentCourseIterator.HasNext())
                {
                    return _currentCourseIterator.Next();
                }

                return null;
            }
        }
    }
    
    //Client Code
    public class Program
    {
        static void Main()
        {
            var mathsCourse = new Course();
            mathsCourse.RegisterStudent(new Student("Pranaya", "1001"));
            mathsCourse.RegisterStudent(new Student("Rout", "1002"));

            var physicsCourse = new Course();
            physicsCourse.RegisterStudent(new Student("Anurag", "1003"));

            var university = new University();
            university.AddCourse(mathsCourse);
            university.AddCourse(physicsCourse);

            var iterator = university.CreateIterator();
            while (iterator.HasNext())
            {
                var student = iterator.Next();
                Console.WriteLine($"Name: {student.Name}, ID: {student.ID}");
            }
            
            Console.ReadLine();
        }
    }
}
Explanation:
  • Each Course has its iterator (CourseIterator), which can be used to iterate over the students of that course.
  • The University has its own iterator (UniversityIterator). This iterator internally leverages the iterators of the individual courses to present a seamless iteration across all students in the university, irrespective of the course they are registered in.
  • This makes fetching all students from the university as simple as iterating over a single list, hiding the complexity of nested structures.
Output:

Real-Time Example of Iterator Design Pattern in C#: University Course Registration System

Through this, the Iterator design pattern simplifies accessing elements across nested collections, making the client’s job easier.

Real-Time Example of Iterator Design Pattern in C#: Social Media Feed

Imagine a social media application where users have various posts: text updates, images, videos, and shared links. As a user, when you open the application, you want to see an aggregated feed of these posts, and you should be able to scroll through them seamlessly, regardless of their type.

using System;
using System.Collections.Generic;

namespace IteratorDesignPattern
{
    //Define the Post
    public abstract class Post
    {
        public string Author { get; set; }
        public DateTime Date { get; set; }
    }

    public class TextUpdate : Post
    {
        public string Text { get; set; }
    }

    public class ImagePost : Post
    {
        public string ImageURL { get; set; }
    }

    public class VideoPost : Post
    {
        public string VideoURL { get; set; }
    }

    public class LinkShare : Post
    {
        public string Link { get; set; }
    }

    //Define the Iterator and Aggregate interfaces
    public interface IIterator<T>
    {
        bool HasNext();
        T Next();
    }

    public interface IAggregate<T>
    {
        IIterator<T> CreateIterator();
    }

    //Implement the Feed
    public class Feed : IAggregate<Post>
    {
        private List<Post> _posts = new List<Post>();

        public void AddPost(Post post)
        {
            _posts.Add(post);
        }

        public IIterator<Post> CreateIterator()
        {
            return new FeedIterator(this);
        }

        private class FeedIterator : IIterator<Post>
        {
            private Feed _feed;
            private int _currentIndex = 0;

            public FeedIterator(Feed feed)
            {
                _feed = feed;
            }

            public bool HasNext()
            {
                return _currentIndex < _feed._posts.Count;
            }

            public Post Next()
            {
                return _feed._posts[_currentIndex++];
            }
        }
    }
    
    //Client Code
    public class Program
    {
        static void Main()
        {
            var userFeed = new Feed();

            userFeed.AddPost(new TextUpdate { Author = "Pranaya", Date = DateTime.Now, Text = "Welcome to Dot Net Tutorials" });
            userFeed.AddPost(new ImagePost { Author = "Kumar", Date = DateTime.Now, ImageURL = "Path/Image.jpg" });
            userFeed.AddPost(new VideoPost { Author = "Roit", Date = DateTime.Now, VideoURL = "Path/Video.mp4" });

            var iterator = userFeed.CreateIterator();

            while (iterator.HasNext())
            {
                var post = iterator.Next();
                if (post is TextUpdate textPost)
                {
                    Console.WriteLine($"{textPost.Author}: {textPost.Text}");
                }
                else if (post is ImagePost imgPost)
                {
                    Console.WriteLine($"{imgPost.Author} shared an image: {imgPost.ImageURL}");
                }
                else if (post is VideoPost videoPost)
                {
                    Console.WriteLine($"{videoPost.Author} shared a video: {videoPost.VideoURL}");
                }
            }

            Console.ReadLine();
        }
    }
}
Explanation:
  • Each Post represents different types of posts on a social media feed.
  • The Feed class represents an aggregated collection of different post types.
  • The FeedIterator provides a mechanism to seamlessly scroll through posts, abstracting the underlying collection’s details.
  • The client code iterates over each post and displays it based on its type.
Output:

Real-Time Example of Iterator Design Pattern in C#: Social Media Feed

Thus, the Iterator Design Pattern allows users to scroll through the mixed content feed without being aware of the various post types, providing a cohesive user experience.

Real-Time Example of Iterator Design Pattern in C#: Restaurant Menu

Suppose a restaurant offers different types of menus: Breakfast, Lunch, and Dinner. Each menu has a list of items. A waiter should be able to print out every item from all the menus sequentially without managing each menu separately.

using System;
using System.Collections.Generic;

namespace IteratorDesignPattern
{
    //Define the MenuItem
    public class MenuItem
    {
        public string Name { get; set; }
        public double Price { get; set; }
    }

    //Define the Iterator and Aggregate interfaces
    public interface IIterator<T>
    {
        bool HasNext();
        T Next();
    }

    public interface IAggregate<T>
    {
        IIterator<T> CreateIterator();
    }

    //Implement the Menus
    public class Menu : IAggregate<MenuItem>
    {
        private List<MenuItem> _items = new List<MenuItem>();

        public void AddItem(MenuItem item)
        {
            _items.Add(item);
        }

        public IIterator<MenuItem> CreateIterator()
        {
            return new MenuIterator(this);
        }

        private class MenuIterator : IIterator<MenuItem>
        {
            private Menu _menu;
            private int _currentIndex = 0;

            public MenuIterator(Menu menu)
            {
                _menu = menu;
            }

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

            public MenuItem Next()
            {
                return _menu._items[_currentIndex++];
            }
        }
    }
    
    //Client Code
    public class Program
    {
        static void Main()
        {
            var breakfastMenu = new Menu();
            breakfastMenu.AddItem(new MenuItem { Name = "Idli", Price = 5.99 });
            breakfastMenu.AddItem(new MenuItem { Name = "Dosa", Price = 6.99 });

            var lunchMenu = new Menu();
            lunchMenu.AddItem(new MenuItem { Name = "Burger", Price = 17.49 });
            lunchMenu.AddItem(new MenuItem { Name = "Salad", Price = 14.99 });

            var menus = new List<Menu> { breakfastMenu, lunchMenu };

            foreach (var menu in menus)
            {
                var iterator = menu.CreateIterator();
                while (iterator.HasNext())
                {
                    var item = iterator.Next();
                    Console.WriteLine($"Item: {item.Name}, Price: ${item.Price}");
                }
            }
            
            Console.ReadLine();
        }
    }
}
Explanation:
  • The MenuItem represents an individual dish on a menu.
  • The Menu class aggregates menu items and provides an iterator (MenuIterator) to traverse them.
  • Using the MenuIterator, a waiter can iterate through all menu items without concern with the internal representation or structure.
  • The client code consolidates different menus and prints out each menu item.
Output:

Real-Time Example of Iterator Design Pattern in C#: Restaurant Menu

Using the Iterator Design Pattern, we’ve abstracted the process of accessing and traversing the menu items, allowing for seamless iteration through diverse collections.

Many of these real-time situations benefit from the Iterator pattern because it encapsulates the traversal logic, promotes separation of concerns, and can help make systems more maintainable and flexible. Moreover, C# provides built-in support for iterators using IEnumerable and IEnumerator interfaces, making it easier to implement and use the Iterator pattern in various scenarios.

When to use the Iterator Design Pattern in C#?

The Iterator Design Pattern is useful when developing software in C# in several scenarios. Here are common situations when you might consider applying the Iterator Pattern:

  • Accessing Elements Without Exposing Internal Structure: If you want to provide a way to access a collection’s elements without exposing its internal representation, the Iterator pattern can be beneficial.
  • Supporting Multiple Traversal Approaches: If your collection can be traversed in multiple ways (e.g., in-order, pre-order, post-order for trees), having distinct iterators for each traversal is helpful.
  • Uniform Interface for Different Collections: When you have different collection types and want a unified way to iterate over all of them, the Iterator pattern provides a consistent interface.
  • Decoupling Algorithms from Containers: If you want to separate algorithms that operate on the data from the data structures (containers) themselves, iterators can help decoupling.
  • Managing Complex Data Structures: You might want to hide the complexity of traversal from the client for complex data structures, like trees or graphs. An iterator abstracts away this complexity.
  • Multiple Simultaneous Traversals: If you need multiple traversals ongoing simultaneously on the same collection, each traversal can use its own iterator without interfering with others.
  • Polymorphic Iteration: When dealing with a composite structure where different elements might need to be iterated over in a polymorphic manner, the Iterator pattern can help.
  • Extensibility: When you anticipate new types of collections or traversal methods in the future, designing with iterators can make the system more extensible.
  • Encapsulation: Iterators can encapsulate specific operations like filtering or transformation. For example, a “filtering iterator” could skip elements that don’t meet a particular condition.
  • Lazy Evaluation: In situations where generating or accessing each item is expensive, and you don’t necessarily need to access all items, an iterator can compute or fetch items on the fly as requested.
When Not To Use Iterator Design Pattern:

While the Iterator pattern has many uses, it’s not always the best choice:

  • If you’re working with simple collections and the built-in iteration mechanisms in C# (like for each) suffice, then introducing custom iterators might be overkill.
  • If adding iterator logic increases the complexity without providing substantial benefits, it might be better to avoid it.

Remember, design patterns provide solutions to common problems, but they should be applied judiciously based on the specific needs and complexity of the problem you’re trying to solve.

In the next article, I will discuss the Observer Design Pattern in C# with Examples. In this article, I try to explain the Real-Time Examples of Iterator Design Patterns using C#. I hope you enjoy these Real-Time Examples of the Iterator Design Patterns in the C# article.

Leave a Reply

Your email address will not be published. Required fields are marked *