In this article I’ll write about the Iterator Design Pattern. This pattern is used to iterate over an aggregate object without exposing the underlying implementation of this object.
More about the pattern
As I mentioned, this pattern is used to iterate over an object which holds a collection of other objects — and you hide the implementation of the iteration from the user who calls the iterator. For a Java example: you can iterate with a for loop the same way through a java.util.List (regardless of the implementation) or a java.util.Set. And for List implementations it does not matter if you have an ArrayList or a LinkedList.
The problem
The problem which is solved with the iterator pattern is the one to create an abstraction above these collection objects to enable different kinds of iteration — without writing the whole set of methods into the interface (for example of java.util.List). Because Java knows generics you may need to support multiple types of objects in your collection — and for this you’d need several methods which would blow-up your interface.
If you want to support three data structures (arrays, lists and maps) and three operations for each collection (sort, merge and find) you would need 3×3 classes to support this functionality (i.e. ArraySorter, ArrayMerger, ArrayFinder, etc.) — which you have to maintain later on.
Although nine classes do not seem very tragical but this is only an example. The generic result would be n x m classes where n is the amount of data structures and m is the amount of algorithms. For 5 different data structures and 8 algorithms you need 40 different classes to maintain. This is bad, isn’t it?
The solution
The solution is to have a generic approach and have only three classes to support your collection algorithms and of course your three structure classes. For the example in the previous section these are 6 classes — which is not much reduction from the 9. However if we look at the second example from the previous section we need only 13 classes to maintain because the generic programming approach requires n + m classes to maintain.
Example
Let’s see a basic example how we can use the iterator pattern to encapsulate our objects and their iteration.
First of all here is a simple data structure which represent a set of elements:
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* This class is the data container holding information we want to iterate through.
*
* @author GHajba
*
*/
public class DataContainer {
private final Set<String> data = new HashSet<>();
public void add(String element) {
this.data.add(element);
}
public Collection<String> getData() {
return this.data;
}
}
Now let’s loop over this list with an Iterator provided by the underlying data of the class:
import java.util.Collection;
import java.util.Iterator;
/**
* This is the main entry point of the application.
*
* @author GHajba
*
*/
public class Main {
public static void main(String... args) {
final DataContainer dc = new DataContainer();
dc.add("Iterator");
dc.add("Design");
dc.add("Pattern");
dc.add("by");
dc.add("Gabor");
dc.add("Hajba");
Collection<String> data = dc.getData();
final Iterator<String> it = data.iterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println();
data.clear();
data = dc.getData();
System.out.println("size of data is " + data.size());
}
}
Finally let’s run this example. Your output may look a bit different as of how a Set stores the elements.
Design Pattern Hajba by Iterator Gabor
size of data is 0
As you can see, in the example above we have to know that the getData() method returns a Collection which already has an iterator. Beside this we can clear the collection of the DataContainer class out of classes where we access the data — which can lead to hard-to-find problems: for example we can delete the contents of the container object.
Let’s find a solution to this problem. A fair approach is to create our own iterator and restrict the access as much as we need it.
The first step could be to create an interface common to all of our container objects. However I’ll skip this because we only have one class in this simple example.
So let’s hide the information in our container and enable access to an iterator which will go through our data set:
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* This class is the data container holding information we want to iterate through.
*
* @author GHajba
*
*/
public class IterableDataContainer {
private final Set<String> data = new HashSet<>();
public void add(String element) {
this.data.add(element);
}
public Iterator<String> getIterator() {
return new DataIterator(this);
}
private Set<String> getData() {
return this.data;
}
private class DataIterator implements Iterator<String> {
private final Iterator<String> iterator;
public DataIterator(IterableDataContainer cont) {
this.iterator = cont.getData().iterator();
}
@Override
public boolean hasNext() {
return this.iterator.hasNext();
}
@Override
public String next() {
if (this.iterator.hasNext()) {
return this.iterator.next();
}
return null;
}
}
}
Now it is time to modify our running script to use the new iterator instead of the old access-based version:
import java.util.Iterator;
/**
* This is the main entry point of the application.
*
* @author GHajba
*
*/
public class Main {
public static void main(String... args) {
final IterableDataContainer dc = new IterableDataContainer();
dc.add("Iterator");
dc.add("Design");
dc.add("Pattern");
dc.add("by");
dc.add("Gabor");
dc.add("Hajba");
final Iterator<String> it = dc.getIterator();
while (it.hasNext()) {
System.out.print(it.next() + " ");
}
System.out.println();
}
}
As you can see, we have no access to the data in the container (you do not know the size and cannot clear the contents). Naturally you can add methods to the IterableDataContainer class to enable access to the size or lear the underlying container — but as you can see, you are in charge and you cannot allow total disposal of elements.
Let’s run the example one more time:
Design Pattern Hajba by Iterator Gabor
Conclusion
The Iterator pattern can be used to create a common way to enable users to iterate through a collection of elements in your container.