DEV Community

Sota
Sota

Posted on

Iterator Pattern

What is Iterator Pattern?

Iterator pattern is a behavioral pattern that provides a way to access the elements of an aggregate (collection) object sequentially without exposing its underlying representation.

When to use it?

  • Use Iterator pattern when you don't want to expose the data structure of your collection.
  • User Iterator pattern when you want one common interface to traverse different data structures instead of having many similar traversal codes for each data structure in your app.

Problem

Company "Alpha Inc." is going to merge with "Beta Inc.". We are in HR department and have to merge their employees data together.
EmployeeListA holds Alpha employees' data, and EmployeeListB has Beta employees' data. Because EmployeeListA uses Array data structure and EmployeeListB uses ArrayList, we write two traversal codes.

Image description

public class HRDepartment {

    public void printEmployee(){
        EmployeeListA employeeListA = new EmployeeListA("Alpha Inc");
        String[] alphaEmployee = employeeListA.getEmployees();

        EmployeeListB employeeListB = new EmployeeListB("Beta Inc");
        List<String> betaEmployee = employeeListB.getEmployees();

        // Traversal code for Array
        for (int i = 0; i < alphaEmployee.length; i++) {
            System.out.println(alphaEmployee[i] + " from Alpha Inc.");
        }

        // Traversal code for ArrayList
        for (int i = 0; i < betaEmployee.size(); i++) {
            System.out.println(betaEmployee.get(i) + " from Beta Inc.");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Can you spot the problems? Here's the issue our code currently has:

  • We are coding to concrete implementation (EmployeeListA and EmployeeListB) not to an interface.
  • If we decide to switch EmployeeListA data structure from Array to another one such as hash map, we need to modify our HRDepartment class a lot.
  • HRDepartment needs to know aggregate's internal structure, in other words, we are exposing our collection's data structure.
  • We have duplication of traversal code for different data structure, Array and ArrayList.

Solution

Encapsulate iteration logic

So where to begin? Let's remove our duplicate iteration codes.
It would be great if we can encapsulate iteration logic and introduce a common interface so that HRDepartment can use just one interface to iterate any data structure.

Image description

We need Iterator interface to decouple iteration logic from HRDepartment.

public interface Iterator {
    boolean hasNext();
    String next();
}
Enter fullscreen mode Exit fullscreen mode

hasNext() returns boolean value indicating whether a collection has next element or not. next() returns next element in a collection. (you will see actual implementation later)

In EmployeeListA/EmployeeListA, We removed getEmployees() method because it exposes our aggregate's data structure. And importantly, we wrote new method createIterator() which creates an corresponding concrete Iterator, for example, EmployeeListA.createIterator() instantiates EmployeeListAIterator.

Introduce common interface for aggregates

Okay, we now encapsulated Iteration logic, our aggregates do not expose their underlying representation, removed duplicate traversal codes. But still one problem remains, HRDepartment depends on concrete aggregates. Let's introduce a common interface for concrete aggregates.

Image description

Structure

Image description

Implementation in Java

I'll omit EmployeeListBIterator and EmployeeListB's implementation as they are similar to those of EmployeeListAIterator and EmployeeListA. If you're not sure how to implement them, you can check in my GitHub repo (link to my repo is at the end of this blog).

public interface Iterator {
    boolean hasNext();
    String next();
}
Enter fullscreen mode Exit fullscreen mode
public class EmployeeListAIterator implements Iterator {

    private String[] employees;
    private int index = 0;

    public EmployeeListAIterator(String[] employees) {
        this.employees = employees;
    }

    @Override
    public boolean hasNext() {
        if (index >= employees.length || employees[index] == null) {
            return false;
        }
        return true;
    }

    @Override
    public String next() {
        String employee = employees[index];
        index++;
        return employee;
    }
}
Enter fullscreen mode Exit fullscreen mode
public interface EmployeeList {
    Iterator createIterator();
    String getCompanyName();
}
Enter fullscreen mode Exit fullscreen mode
public class EmployeeListA implements EmployeeList{

    private String companyName;
    private String[] employees;
    private int SIZE = 5;
    private int index = 0;

    public EmployeeListA(String companyName) {
        this.companyName = companyName;
        employees = new String[SIZE];
        addEmployee("Alice");
        addEmployee("Alisha");
        addEmployee("Alex");
    }

    public void addEmployee(String name) {
        employees[index] = name;
        index++;
    }

    public Iterator createIterator() {
        return new EmployeeListAIterator(employees);
    }

    public String getCompanyName() {
        return companyName;
    }
}
Enter fullscreen mode Exit fullscreen mode
public class HRDepartment {

    EmployeeList listA;
    EmployeeList listB;

    public HRDepartment(EmployeeList listA, EmployeeList listB) {
        this.listA = listA;
        this.listB = listB;
    }

    public void printEmployee() {
        Iterator listAIterator = listA.createIterator();
        Iterator listBIterator = listB.createIterator();

        System.out.println("--- Employees from " + listA.getCompanyName() + " ---");
        printEmployee(listAIterator);
        System.out.println("--- Employees from " + listB.getCompanyName() + " ---");
        printEmployee(listBIterator);
    }

    private void printEmployee(Iterator iterator) {
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

--- Employees from Alpha Inc ---
Alice
Alisha
Alex
--- Employees from Beta Inc ---
Bob
Bella
Benjamin
Enter fullscreen mode Exit fullscreen mode

Pitfalls

  • Iterator itself imply no ordering in which elements are visited during iteration. The order of iteration depends on the underlying data structure being traversed, not on the Iterator.
  • Iterator does not support accessing elements by index.

Iterator & Iterable interface in Java

Before the end of this blog, let's check how we can use java.util.Iterator.

Image description

All Collection classes such as ArrayList, implement the Collection interface, which extends Iterable interface. Thus every Collection class is Iterable.

public class IteratorDemo {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>(List.of(0, 1, 2, 3));
        Iterator<Integer> iterator = nums.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

You can check all the design pattern implementations here.
GitHub Repository

Top comments (0)