Introduction

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 a collection of objects in a uniform manner.

Key Concepts

  • Iterator: An interface or abstract class that defines methods for accessing and traversing elements.
  • Concrete Iterator: A class that implements the Iterator interface and is responsible for the actual traversal of the collection.
  • Aggregate: An interface or abstract class that defines methods for creating an iterator object.
  • Concrete Aggregate: A class that implements the Aggregate interface and returns an instance of the Concrete Iterator.

Structure

Class Diagram

Here is a simple class diagram to illustrate the Iterator pattern:

+-----------------+       +-------------------+
|     Iterator    |       |   ConcreteIterator|
+-----------------+       +-------------------+
| +hasNext(): bool|<------| +hasNext(): bool  |
| +next(): Object |       | +next(): Object   |
+-----------------+       +-------------------+
        ^                         ^
        |                         |
+-----------------+       +-------------------+
|    Aggregate    |       |  ConcreteAggregate|
+-----------------+       +-------------------+
| +createIterator():      | +createIterator():|
|   Iterator              |   Iterator         |
+-----------------+       +-------------------+

Example

Let's consider an example where we have a collection of books and we want to iterate through them using the Iterator pattern.

Step 1: Define the Iterator Interface

public interface Iterator {
    boolean hasNext();
    Object next();
}

Step 2: Create the Concrete Iterator

public class BookIterator implements Iterator {
    private Book[] books;
    private int position = 0;

    public BookIterator(Book[] books) {
        this.books = books;
    }

    @Override
    public boolean hasNext() {
        return position < books.length && books[position] != null;
    }

    @Override
    public Object next() {
        return books[position++];
    }
}

Step 3: Define the Aggregate Interface

public interface Aggregate {
    Iterator createIterator();
}

Step 4: Create the Concrete Aggregate

public class BookCollection implements Aggregate {
    private Book[] books;
    private int index = 0;

    public BookCollection(int size) {
        books = new Book[size];
    }

    public void addBook(Book book) {
        if (index < books.length) {
            books[index++] = book;
        }
    }

    @Override
    public Iterator createIterator() {
        return new BookIterator(books);
    }
}

Step 5: Define the Book Class

public class Book {
    private String title;

    public Book(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }
}

Step 6: Use the Iterator

public class Main {
    public static void main(String[] args) {
        BookCollection bookCollection = new BookCollection(5);
        bookCollection.addBook(new Book("Design Patterns"));
        bookCollection.addBook(new Book("Refactoring"));
        bookCollection.addBook(new Book("Clean Code"));

        Iterator iterator = bookCollection.createIterator();
        while (iterator.hasNext()) {
            Book book = (Book) iterator.next();
            System.out.println("Book Title: " + book.getTitle());
        }
    }
}

Practical Exercises

Exercise 1: Implement an Iterator for a List of Integers

Create a class IntegerList that holds a list of integers and implement an iterator to traverse through the list.

Solution

public class IntegerListIterator implements Iterator {
    private List<Integer> integers;
    private int position = 0;

    public IntegerListIterator(List<Integer> integers) {
        this.integers = integers;
    }

    @Override
    public boolean hasNext() {
        return position < integers.size();
    }

    @Override
    public Object next() {
        return integers.get(position++);
    }
}

public class IntegerList implements Aggregate {
    private List<Integer> integers = new ArrayList<>();

    public void addInteger(int integer) {
        integers.add(integer);
    }

    @Override
    public Iterator createIterator() {
        return new IntegerListIterator(integers);
    }
}

public class Main {
    public static void main(String[] args) {
        IntegerList integerList = new IntegerList();
        integerList.addInteger(1);
        integerList.addInteger(2);
        integerList.addInteger(3);

        Iterator iterator = integerList.createIterator();
        while (iterator.hasNext()) {
            System.out.println("Integer: " + iterator.next());
        }
    }
}

Exercise 2: Implement a Reverse Iterator

Modify the BookIterator to iterate through the collection in reverse order.

Solution

public class ReverseBookIterator implements Iterator {
    private Book[] books;
    private int position;

    public ReverseBookIterator(Book[] books) {
        this.books = books;
        this.position = books.length - 1;
    }

    @Override
    public boolean hasNext() {
        return position >= 0 && books[position] != null;
    }

    @Override
    public Object next() {
        return books[position--];
    }
}

Common Mistakes and Tips

  • Not Checking Bounds: Ensure that your hasNext() method correctly checks the bounds of the collection to avoid ArrayIndexOutOfBoundsException.
  • Modifying Collection During Iteration: Avoid modifying the collection while iterating through it, as this can lead to inconsistent behavior.
  • Type Safety: Use generics to ensure type safety in your iterators.

Conclusion

The Iterator pattern is a powerful tool for traversing collections in a uniform manner. By abstracting the traversal logic, it allows you to work with different types of collections without needing to know their internal structure. This pattern is widely used in Java's Collection Framework and is essential for any software developer to understand.

In the next section, we will explore the Mediator pattern, another behavioral pattern that helps reduce the complexity of communication between multiple objects.

© Copyright 2024. All rights reserved