The iterator pattern simplifies interactions between various objects that use different types of Collections
to store data. Clients interacting with these objects shouldn’t need to know the internal details of how the data is stored, they should just be able to fetch the data by programming to interface not to implementation.
Example Problem:
Here’s a couple of example objects that store the same type of data using different types of collections (SnookerPlayers
which uses an Array
and PoolPlayers
which uses an ArrayList
). A client would need to implement two different loops (specific to the collection type) in order to access the desired data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | public class Player { private String name; private double fargoRating; public Player(String name) { this.name = name; } public String getName() { return name; } public double getFargoRating() { return fargoRating; } // omitting the details of fargo rating private void updateFargoRating() { } } public class PoolPlayers { private final ArrayList<Player> poolPlayers; public PoolPlayers() { poolPlayers = new ArrayList<Player>() {{ add(new Player("John Morra")); add(new Player("Shane Van Boening")); }}; } public ArrayList<Player> getPoolPlayers() { return poolPlayers; } } public class SnookerPlayers { private Player[] snookerPlayers; public SnookerPlayers() { snookerPlayers = new Player[16]; // small league with one table snookerPlayers[0] = new Player("Mark Selby"); snookerPlayers[1] = new Player("Judd Trump"); } public Player[] getSnookerPlayers() { return snookerPlayers; } } |
Consider league software attempting to print a full list of players:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class League { SnookerPlayers snookerPlayers = new SnookerPlayers(); PoolPlayers poolPlayers = new PoolPlayers(); public League() { printNames(); } public void printNames() { for (int i = 0; i < snookerPlayers.getSnookerPlayers().length; i++) { String name = snookerPlayers.getSnookerPlayers()[i].getName(); double fargoRating = snookerPlayers.getSnookerPlayers()[i].getFargoRating(); System.out.println(String.format("Name: %s, Fargo Rating: %s", name, fargoRating)); } for (int i = 0; i < poolPlayers.getPoolPlayers().size(); i++) { String name = poolPlayers.getPoolPlayers().get(i).getName(); double fargoRating = poolPlayers.getPoolPlayers().get(i).getFargoRating(); System.out.println(String.format("Name: %s, Fargo Rating: %s", name, fargoRating)); } } } |
The loops are very closely duplicated with the exception of the methods calls specific to the collection used (get()
for ArrayList
and [i]
for Array
). The League
code can be simplified by implementing the Iterator Pattern. Let’s start by having SnookerPlayer
implement the Java Iterator
interface and adding the necessary logic. SnookerPlayer
now looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | public class SnookerPlayers implements Iterator { private Player[] snookerPlayers; private int position = 0; public SnookerPlayers() { snookerPlayers = new Player[16]; // small league with one table snookerPlayers[0] = new Player("Mark Selby"); snookerPlayers[1] = new Player("Judd Trump"); } public Player[] getSnookerPlayers() { return snookerPlayers; } @Override public boolean hasNext() { return position >= snookerPlayers.length || snookerPlayers[position] == null; } @Override public Object next() { Player player = snookerPlayers[position]; position += 1; return player; } @Override public void remove() { if (position <= 0) { throw new IllegalStateException("Cannot remove without calling next()"); } if (snookerPlayers[position - 1] != null) { for (int i = position -1; i < (snookerPlayers.length - 1); i++) { snookerPlayers[i] = snookerPlayers[i+1]; } snookerPlayers[snookerPlayers.length - 1] = null; } } } |
Luckily our PoolPlayer
doesn’t need to provide it’s own implementation of Iterator
because ArrayList
already provides one (iterator()
). Now that we have a common Iterator
interface. The League
class can now be greatly simplified:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class League { SnookerPlayers snookerPlayers = new SnookerPlayers(); PoolPlayers poolPlayers = new PoolPlayers(); public League() { printNames(snookerPlayers); // now an iterator printNames(poolPlayers.getPoolPlayers().iterator()); } public void printNames(Iterator iterator) { while (iterator.hasNext()) { Player player = (Player) iterator.next(); System.out.println(String.format("Name: %s, FargoRating: %s", player.getName(), player.getFargoRating())); } } } |
Keep in mind you can write your own Iterator
to support any type of Collection
. If you need to write your own Iterator
simply expose the Iterator
on your class by creating a getter method.
Summary
Many of the Java Collections
expose an Iterator
which eases the implementation of this design pattern. Implementing this pattern can minimize the amount of responsibility a class has ameliorating the single responsibility principle. The above code could be simplified (Player
should really be an interface
) but I feel it’s a good reference example of the Iterator Pattern.