Introduction
The Iterator Pattern is a design pattern that provides a way to iterate through a collection of objects without exposing the underlying representation of the collection. It involves two key components: the Iterator and the Iterable collection. The Iterator provides methods to traverse the collection, and the Iterable collection provides a method to get an Iterator.
Motivation
In software development, collections of objects, like lists, trees, sets, and maps, are the commonly used data structures. Direct access to the elements within these collections can lead to issues such as unexpected modifications, exposure of internal structure, and increased coupling between components. The Iterator Pattern helps in managing such complexities by providing a standardized way to iterate these collections sequentially without exposing the internal details.
Using Java as an example, before the introduction of iterators, iteration through a collection was not generic. for lists, we use a loop and it won't work for a map or a tree because it does not store data in sequence.
Implementation
Imagine we are building a system for a restaurant that helps to show all the food on their menu, we can use an iterator pattern to show this as a list, so let's dig in.
This interface defines a method createIterator() that creates an iterator for the menu.
public interface Menu { MenuIterator createIterator(); } MenuItem represents individual menu items (food) with attributes like name, description, and price.
public class MenuItem { String name; String description; double price; public MenuItem(String name, String description, double price) { this.name = name; this.description = description; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } } The MenuIterator declares methods hasNext() to check if there are more items in the menu and next() to retrieve the next item in the menu.
public interface MenuIterator { boolean hasNext(); MenuItem next(); } BreakfastMenu represents a concrete implementation of the menu interface we created before with a collection of menu items. It has an addItem() method to add menu items to the list during initialization.
public class BreakfastMenu implements Menu { private ArrayList<MenuItem> menuItems = new ArrayList<>(); public BreakfastMenu() { addItem("Pancake", "Pancake with chocolate cream", 2.99); addItem("Waffles", "Crispy Golden Waffle", 2.59); addItem("Coffee", "Ice cold coffee", 1.99); } private void addItem(String name, String description, double price) { menuItems.add(new MenuItem(name, description, price)); } @Override public MenuIterator createIterator() { return new BreakfastMenuIterator(menuItems); } } Implements the MenuIterator interface and serves as an iterator for the breakfast menu. It maintains the current position while iterating through menu items.
public class BreakfastMenuIterator implements MenuIterator { private ArrayList<MenuItem> menuItems; private int position = 0; public BreakfastMenuIterator(ArrayList<MenuItem> menuItems) { this.menuItems = menuItems; } @Override public boolean hasNext() { return position < menuItems.size(); } @Override public MenuItem next() { return menuItems.get(position++); } } Here, BreakfastMenu is created, and a BreakfastMenuIterator is obtained using the createIterator() method. The displayMenu() function would be used to print the menu items as shown below.
BreakfastMenu breakfastMenu = new BreakfastMenu(); MenuIterator breakfastIterator = breakfastMenu.createIterator(); System.out.println("Breakfast Menu:"); displayMenu(breakfastIterator); Breakfast Menu: Pancake: Pancake with chocolate cream - $2.99 Waffles: Crispy Golden Waffle - $2.59 Coffee: Ice cold coffee - $1.99 Advantages:
- It encloses the internal structure of how the elements are maintained.
- Generic Interface: Provides a generic interface for traversing different collections.
- Allows for more sophisticated traversal and filtering capabilities.
Disadvantages:
- Introduces an additional layer of abstraction, which can add overhead.
- It might be unnecessary for simple collections, adding undue complexity.
Usecase
A real-world use case of the Iterator Pattern is in menu items in a restaurant. Different menus like breakfast, lunch, and dinner can be iterated over without exposing the details of how these menu items are maintained, whether it be in a list, array, or any other collection.
Conclusion
The Iterator Pattern is instrumental in providing a clean API to the client for iterating various collections, ensuring that the internal details and representation of the collections are well encapsulated. It promotes the Single Responsibility Principle by separating the traversal logic from the business logic of the collection.