Iterator

Intent

Problem

Need to "abstract" the traversal of wildly different data structures so that algorithms can be defined that are capable of interfacing with each transparently.

Structure Summary

Structure category:  
promote X to "full object status"
Similar patterns:   Prototype   Command   Mediator   Memento

Discussion

"An aggregate object such as a list should give you a way to access its elements without exposing its internal structure. Moreover, you might want to traverse the list in different ways, depending on what you need to accomplish. But you probably don't want to bloat the List interface with operations for different traversals, even if you could anticipate the ones you'll require. You might also need to have more than one traversal pending on the same list." [GoF, p257] And, providing a uniform interface for traversing many types of aggregate objects (i.e. polymorphic iteration) might be valuable.

The Iterator pattern lets you do all this. The key idea is to take the responsibility for access and traversal out of the aggregate object and put it into an Iterator object that defines a standard traversal protocol.

The Iterator abstraction is fundamental to an emerging technology called "generic programming". This strategy seeks to explicitly separate the notion of "algorithm" from that of "data structure". The motivation is to: promote component-based development, boost productivity, and reduce configuration management.

As an example, if you wanted to support four data structures (array, binary tree, linked list, and hash table) and three algorithms (sort, find, and merge), a traditional approach would require four times three permutations to develop and maintain. Whereas, a generic programming approach would only require four plus three configuration items.

Structure

The Client uses the Collection class’ public interface directly. But access to the Collection’s elements is encapsulated behind the additional level of abstraction called Iterator. Each Collection derived class knows which Iterator derived class to create and return. After that, the Client relies on the interface defined in the Iterator base class.

Example

The Iterator provides ways to access elements of an aggregate object sequentially without exposing the underlying structure of the object. Files are aggregate objects. In office settings where access to files is made through administrative or secretarial staff, the Iterator pattern is demonstrated with the secretary acting as the Iterator. Several television comedy skits have been developed around the premise of an executive trying to understand the secretary's filing system. To the executive, the filing system is confusing and illogical, but the secretary is able to access files quickly and efficiently. [Michael Duell, "Non-software examples of software design patterns", Object Magazine, Jul 97, p54]

On early television sets, a dial was used to change channels. When channel surfing, the viewer was required to move the dial through each channel position, regardless of whether or not that channel had reception. On modern television sets, a next and previous button are used. When the viewer selects the "next" button, the next tuned channel will be displayed. Consider watching television in a hotel room in a strange city. When surfing through channels, the channel number is not important, but the programming is. If the programming on one channel is not of interest, the viewer can request the next channel, without knowing its number.

Check list

  1. Add a create_iterator() method to the "collection" class, and grant the "iterator" class privileged access.
  2. Design an "iterator" class that can encapsulate traversal of the "collection" class.
  3. Clients ask the collection object to create an iterator object.
  4. Clients use the first(), is_done(), next(), and current_item() protocol to access the elements of the collection class.

Before and after

BeforeAfter
// The class SomeClassWithData provides access
// to its internal data structure.  Clients can
// accidently or maliciously trash that data
// structure.

class SomeClassWithData {
   private TreeSet<Integer> m_data =
                   new TreeSet<Integer>();

   public void add( int in ) {
      m_data.add( in ); }
   public Collection get_data() {
      return m_data; }
}

class IteratorDemo {
   public static void main( String[] args ) {
      SomeClassWithData some_object =
                  new SomeClassWithData();
      for (int i=9; i > 0; --i)
         some_object.add( i );
      Collection data = some_object.get_data();
      for (java.util.Iterator it = data.iterator();
           it.hasNext(); )
         System.out.print( it.next() + "  " );
      System.out.println();

      // Do we really want a client to be able to
      // trash encapsulated state?
      data.clear();
      data = some_object.get_data();
      System.out.println( "size of data is "
                          + data.size() );
}  }

// 1  2  3  4  5  6  7  8  9
// size of data is 0
  
// Take traversal-of-a-collection functionality out
// of the collection and promote it to "full object
// status".  This simplifies the collection, allows
// many traversals to be active simultaneously, and
// decouples collection algorithms from collection
// data structures.

class SomeClassWithData {
   private TreeSet<Integer> m_data =
                   new TreeSet<Integer>();

   public class Iterator {
      private SomeClassWithData m_collection;
      private java.util.Iterator m_it;
      private int m_current;
      public Iterator( SomeClassWithData in ) {
         m_collection = in;
      }
      public void first() {
         m_it = m_collection.m_data.iterator();
         next();
      }
      public void next() {
         try {
            m_current = m_it.next();
         } catch (NoSuchElementException ex) {
            m_current = -999;
      }  }
      public boolean is_done() {
         return m_current == -999;
      }
      public int current_item() { return m_current; }
   }

   public void add( int in ) { m_data.add( in ); }
   public Iterator create_iterator() {
      return new Iterator( this );
}  }

class IteratorDemo {
   public static void main( String[] args ) {
      SomeClassWithData some_object =
                   new SomeClassWithData();
      for (int i=9; i > 0; --i)
         some_object.add( i );

      // get_data() has been removed.
      // Client has to use Iterator.
      SomeClassWithData.Iterator it1 =
                    some_object.create_iterator();
      SomeClassWithData.Iterator it2 =
                    some_object.create_iterator();

      for (it1.first(); ! it1.is_done(); it1.next())
         System.out.print( it1.current_item() + "  " );
      System.out.println();

      // Two simultaneous iterations
      for (it1.first(), it2.first();  ! it1.is_done();
           it1.next(), it2.next())
         System.out.print( it1.current_item() + " "
                         + it2.current_item() + "  " );
      System.out.println();
}  }

// 1  2  3  4  5  6  7  8  9
// 1 1  2 2  3 3  4 4  5 5  6 6  7 7  8 8  9 9

Rules of thumb

The abstract syntax tree of Interpreter is a Composite (therefore Iterator and Visitor are also applicable). [GoF, p255]

Iterator can traverse a Composite. Visitor can apply an operation over a Composite. [GoF, p173]

Polymorphic Iterators rely on Factory Methods to instantiate the appropriate Iterator subclass. [GoF, p271]

Memento is often used in conjunction with Iterator. An Iterator can use a Memento to capture the state of an iteration. The Iterator stores the Memento internally. [GoF, p271]