Iterator

Through the Iterator pattern we can access sequentially all the elements of an aggregate object without exposing its underlying representation. The Iterator takes over from the aggregate the responsibility of traversing it, simplifying the aggregate interface and implementation.

The classic methods provided by an iterator are: first(), next(), isDone(), and currentItem(). Typically in Java these four methods are reduced to: next() and hasNext(); and java.util.Iterator sports even a remove() method used to modify the undelying data.

In my humble opinion adding remove() in the standard java.util.Iterator has not been a wonderful idea. Notice that the API documentation says remove() is a optional operation so an UnsupportedOperationException could be thrown if it is not implemented. Besides, we could get an IllegalStateException if the iterator has problem in performing the operation.

Not considering remove(), the fundamental difference in the two sets of operation is that the second does not have a way to get back to the beginning of the collection, since there is no first() method or anything equivalent. So this kind of iterators are basically for one time usage.

The first implementation of the Iterator pattern in Java is java.util.Enumerator, still available for compatibility reason but not much used nowadays. It provides just two methods, hasMoreElements() and nextElements(), equivalents to hasNext() and next().

In the typical Java implementation of Iterator, hasNext() checks if there are more elements in the collection, and next() returns the next one. Since at creation an iterator is pointing to the fake position before the first element, calling next() at that moment would return the first element of the collection, if available.

Here is an example where the Iterator pattern comes in handy: we need to manage two menues that are very similar, are based on the same class, MenuItem, but they rely on different data structure. The class Waitress should print the list of the items available for both of them, MenuPancake and MenuDiner. But, since one is using an ArrayList and the other a low level array, looks like there is no way of access the two collections in the same way.

As usual in computer sciences, we solve the problem introducing an extra layer. And in this case this role is played by the iterator. We don't access directly the collections, but we use iterators to them. It would be each iterator task taking care of the low level details, exposing to the client just the common behavior.

The core of our system is the item used to describe the dish on the menu, the MenuItem class:
public class MenuItem {
    private String name;
    private String descr;
    private boolean veg;
    private double price;

    public MenuItem(String name, String descr, boolean veg, double price) {
        this.name = name;
        this.descr = descr;
        this.veg = veg;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public String getDescr() {
        return descr;
    }

    public double getPrice() {
        return price;
    }

    public boolean isVeg() {
        return veg;
    }
}
Besides the usage of this basic data structure, we are asking to our menus only to give us a way to access its items through an iterator. We make explicit this requirement saying that any menu should implement this interface:
import java.util.Iterator;

public interface Menu {
    public Iterator iterator();
}
An interesting point is that the Pancake menu is using an ArrayList collection, and that makes available its own iterator that we can use for our needing. But the Diner menu is using a raw array that, as one could expect, does not provide such a fancy feature like an iterator.

We solve this issue providing a custom iterator for this case:
import java.util.Iterator;

public class IteratorDiner implements Iterator {
    MenuItem[] menu;
    int pos = 0;

    public IteratorDiner(MenuItem[] menu) {
        this.menu = menu;
    }

    public boolean hasNext() {
        if(pos < menu.length && menu[pos] != null)
            return true;
        else
            return false;
    }

    public MenuItem next() {
        return menu[pos++];
    }

    public void remove() {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}
I'm not interested in activating the remove() support in this iterator, so I leave it throwing an exception.

So, here is our pancake menu:
import java.util.ArrayList;
import java.util.Iterator;

public class MenuPancake implements Menu {
    private ArrayList<MenuItem> menu;

    public MenuPancake() {
        menu = new ArrayList<MenuItem>();
        add("M1", "Sausage pancake", false, 2.99);
        add("V1", "Veggie pancake", true, 2.99);
        add("V2", "Blueberry pancake", true, 3.49);
        add("V3", "Waffle", true, 3.49);
    }

    public final void add(String name, String desc, boolean veg, double price) {
        MenuItem item = new MenuItem(name, desc, veg, price);
        menu.add(item);
    }

    public Iterator iterator() {
        return menu.iterator();
    }
// ...
}
And this is the diner menu:
import java.util.Iterator;

public class MenuDiner implements Menu {
    private static final int MAX_ITEMS = 6;
    private int nrItems = 0;
    private MenuItem[] menu;

    public MenuDiner() {
        menu = new MenuItem[MAX_ITEMS];

        add("V1", "Veggie stuff", true, 2.99);
        add("M1", "Hamburger", true, 2.99);
        add("M2", "Soup", true, 3.49);
        add("M3", "Hotdog", true, 3.99);
    }

    public final void add(String name, String desc, boolean veg, double price) {
        if (nrItems < MAX_ITEMS) {
            menu[nrItems++] = new MenuItem(name, desc, veg, price);
        } else {
            System.err.println("Can't add more items to the menu!");
        }
    }

    public Iterator iterator() {
        return new IteratorDiner(menu);
    }

    // ...
}
In this way the Waitress class is not interested in the implementation details of its client classes, it just relies on the iterator:
import java.util.Iterator;

public class Waitress {
    private Menu menuDiner;
    private Menu menuPancake;

    public Waitress(MenuDiner menuDiner, MenuPancake menuPancake) {
        this.menuDiner = menuDiner;
        this.menuPancake = menuPancake;
    }

    public void printMenu() {
        Iterator itPancake = menuPancake.iterator();
        System.out.println("Menu breakfast:");
        printMenu(itPancake);

        Iterator itDiner = menuDiner.iterator();
        System.out.println("Menu lunch:");
        printMenu(itDiner);
    }

    private void printMenu(Iterator it) {
        while(it.hasNext()) {
            MenuItem item = (MenuItem)it.next();
            System.out.print(item.getName() + ", ");
            System.out.print(item.getPrice() + " -- ");
            System.out.println(item.getDescr());
        }
    }
}
To test the code I wrote this few lines:
MenuDiner mDiner = new MenuDiner();
MenuPancake mPancake = new MenuPancake();

Waitress w = new Waitress(mDiner, mPancake);
w.printMenu();

If you want to get more details on the iterator pattern, I suggest you to read chapter nine of Head First Design Patterns. I jotted down this lines while reading it.

No comments:

Post a Comment