The Iterator Pattern is a behavioral design pattern that provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation. This pattern is particularly useful when you need to traverse through a collection of objects, such as lists or arrays, in a uniform way.
Important Components
- Iterator: An interface or abstract class defining methods for traversing a collection, typically hasNext() (checks if more elements exist) and next() (returns the next element).
- Concrete Iterator: A class implementing the Iterator interface, maintaining the current position and handling traversal for a specific collection.
- Aggregate: An interface or abstract class defining a method to create an Iterator for the collection.
- Concrete Aggregate: A class implementing the Aggregate interface, representing the collection (e.g., list, array) and providing a way to create its Iterator.
- Client: Uses the Iterator to traverse the collection without needing to know its internal structure.
How It Works
- The Aggregate defines a method (e.g., createIterator()) to return an Iterator for its collection.
- The Concrete Aggregate implements this method, creating a Concrete Iterator specific to its structure.
- The Concrete Iterator keeps track of the current position and implements hasNext() and next() to traverse the collection.
- The Client obtains the Iterator from the Aggregate and uses it to iterate over the collection, calling hasNext() and next() in a loop.
- This decouples the client from the collection’s implementation, allowing different collections to be traversed uniformly.
Sample Implementation
// Iterator Interface
interface Iterator<T> {
boolean hasNext();
T next();
}
// Aggregate Interface
interface BookCollection {
Iterator<Book> createIterator();
}
// Book Class (Element in the Collection)
class Book {
private String title;
public Book(String title) {
this.title = title;
}
public String getTitle() {
return title;
}
@Override
public String toString() {
return title;
}
}
// Concrete Aggregate
class Library implements BookCollection {
private List<Book> books;
public Library() {
books = new ArrayList<>();
}
public void addBook(Book book) {
books.add(book);
}
@Override
public Iterator<Book> createIterator() {
return new LibraryIterator(books);
}
}
// Concrete Iterator
class LibraryIterator implements Iterator<Book> {
private List<Book> books;
private int currentIndex;
public LibraryIterator(List<Book> books) {
this.books = books;
this.currentIndex = 0;
}
@Override
public boolean hasNext() {
return currentIndex < books.size();
}
@Override
public Book next() {
if (!hasNext()) {
throw new NoSuchElementException("No more books to iterate.");
}
return books.get(currentIndex++);
}
}
// Usage
public class Main {
public static void main(String[] args) {
// Create a library (Concrete Aggregate)
Library library = new Library();
library.addBook(new Book("The Pragmatic Programmer"));
library.addBook(new Book("Clean Code"));
library.addBook(new Book("Code Complete"));
// Get iterator from library
Iterator<Book> iterator = library.createIterator();
// Traverse the collection using the iterator
System.out.println("Books in the library:");
while (iterator.hasNext()) {
Book book = iterator.next();
System.out.println("- " + book.getTitle());
}
}
}
/*
Books in the library:
- The Pragmatic Programmer
- Clean Code
- Code Complete
*/
Code language: PHP (php)
Use case and Implementation
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
//IteratorDemo.java import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; // Define the Account Class class Account { private String accountNumber; private String accountHolderName; private double balance; public Account(String accountNumber, String accountHolderName, double balance) { this.accountNumber = accountNumber; this.accountHolderName = accountHolderName; this.balance = balance; } public String getAccountNumber() { return accountNumber; } public String getAccountHolderName() { return accountHolderName; } public double getBalance() { return balance; } @Override public String toString() { return "Account{" + "accountNumber='" + accountNumber + '\'' + ", accountHolderName='" + accountHolderName + '\'' + ", balance=" + balance + '}'; } } // Create the AccountCollection Interface interface AccountCollection { AccountIterator createIterator(); } // Implement the AccountCollectionImpl Class class AccountCollectionImpl implements AccountCollection { private List<Account> accountList; public AccountCollectionImpl() { accountList = new ArrayList<>(); } public void addAccount(Account account) { accountList.add(account); } public void removeAccount(Account account) { accountList.remove(account); } @Override public AccountIterator createIterator() { return new AccountIteratorImpl(accountList); } } // Define the AccountIterator Interface interface AccountIterator { boolean hasNext(); Account next(); } // Implement the AccountIteratorImpl Class class AccountIteratorImpl implements AccountIterator { private List<Account> accountList; private int position; public AccountIteratorImpl(List<Account> accountList) { this.accountList = accountList; this.position = 0; } @Override public boolean hasNext() { return position < accountList.size(); } @Override public Account next() { if (!hasNext()) { throw new NoSuchElementException(); } return accountList.get(position++); } } // Demo class to run the example public class IteratorDemo { public static void main(String[] args) { AccountCollectionImpl accountCollection = new AccountCollectionImpl(); accountCollection.addAccount(new Account("123", "Rajesh", 1000.0)); accountCollection.addAccount(new Account("456", "Chakrapani", 2000.0)); accountCollection.addAccount(new Account("789", "Mahesh", 3000.0)); AccountIterator iterator = accountCollection.createIterator(); while (iterator.hasNext()) { Account account = iterator.next(); System.out.println(account); } } } /* C:\>javac IteratorDemo.java C:\>java IteratorDemo Account{accountNumber='123', accountHolderName='Rajesh', balance=1000.0} Account{accountNumber='456', accountHolderName='Chakrapani', balance=2000.0} Account{accountNumber='789', accountHolderName='Mahesh', balance=3000.0} */
Advantages
- Encapsulation: Hides the collection’s internal structure, exposing only the traversal mechanism.
- Uniformity: Provides a consistent way to traverse different types of collections (e.g., arrays, lists, trees).
- Flexibility: Supports multiple simultaneous traversals by creating multiple iterators.
- Single Responsibility: Separates traversal logic from the collection’s core functionality.
- Extensibility: New collection types can implement the Aggregate interface without affecting clients.
Disadvantages
- Complexity: Adds extra classes and interfaces, increasing code complexity for simple collections.
- Performance Overhead: Iterators may introduce slight overhead compared to direct access (e.g., array indexing).
- Limited Operations: The basic Iterator interface only supports sequential access; additional methods (e.g., remove()) require extensions.
- State Management: Iterators must maintain traversal state, which can be error-prone in concurrent environments.
When to Use
- When you need to traverse a collection without exposing its internal representation.
- When you want to provide a uniform way to iterate over different collection types.
- When you need to support multiple traversal strategies (e.g., forward, backward, filtered).
- When you want to decouple the traversal logic from the collection’s implementation.
Real-World Example
- Java Collections Framework: Java’s Iterator interface (in java.util) is used by collections like ArrayList, HashSet, and TreeMap. For example, list.iterator() returns an Iterator for traversal.
- Database Cursors: Database query results are traversed using cursors, which act like iterators over rows.
- File Systems: Iterating over files in a directory without exposing the underlying file system structure.
- Web Frameworks: Iterating over HTTP request parameters or JSON objects in a uniform way.
The Iterator Pattern is a behavioral design pattern that provides a standardized way to access the elements of a collection sequentially without exposing its underlying structure. It encapsulates the traversal logic, allowing clients to iterate over different types of collections uniformly.This pattern enhances flexibility, encapsulation, and code reusability. It allows you to create multiple iterators over the same collection, traverse it in different ways, or even pause and resume iteration.