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.
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.");
}
}
}
Can you spot the problems? Here's the issue our code currently has:
- We are coding to concrete implementation (
EmployeeListA
andEmployeeListB
) 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 ourHRDepartment
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.
We need Iterator
interface to decouple iteration logic from HRDepartment
.
public interface Iterator {
boolean hasNext();
String next();
}
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.
Structure
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();
}
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;
}
}
public interface EmployeeList {
Iterator createIterator();
String getCompanyName();
}
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;
}
}
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());
}
}
}
Output:
--- Employees from Alpha Inc ---
Alice
Alisha
Alex
--- Employees from Beta Inc ---
Bob
Bella
Benjamin
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
.
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());
}
}
}
You can check all the design pattern implementations here.
GitHub Repository
Top comments (0)