Chapter 7. Iterator Pattern

<feature><title>In This Chapter</title> </feature>

Nearly every application uses collections. A collection is simply a group of organized data. ActionScript has lots of collections including standard types such as arrays and associative arrays as well as more sophisticated collections such as multidimensional arrays and custom collection data types. When working with collections, you will naturally need to access the elements of that collection. The Iterator pattern described in this chapter does all that while avoiding some of the pitfalls that other approaches to data access might present.

Understanding the Problems with Iteration

For the purposes of this early discussion of iterating over collection data, we’ll use the following class to illustrate points. The customized UIntCollection class is essentially a glorified array. However, although an array can store any sort of data, the UIntCollection stores only data of type uint.

package com.peachpit.aas3wdp.collections {
   public class UIntCollection {
      private var _data:Array;

      public function UIntCollection() {
         _data = new Array();
      }

      public function addElement(value:uint):void {
         _data.push(value);
      }

   }
}

Iterating over collection data presents several common dilemmas. One dilemma deals with how an object allows access to the collection. One option is to simply expose the collection.

For example, if a class has an array property, it can expose the array using a getter method. Adding the following getter method to the UIntCollection class does just that:

public function get data():Array {
   return _data;
}

The preceding solution enables you to iterate through the collection using a for statement, as follows:

var collection:UIntCollection = new UIntCollection ();
collection.addElement(1);
collection.addElement(2);
var i:uint;
for(i = 0; i < collection.data.length; i++) {
   // Code that uses collection data.
   trace(collection.data[i]);
}

There are two major related flaws with the preceding solution. The first is that exposing the array directly breaks encapsulation in a fundamental way. The intention of exposing the collection by way of a getter method is to enable iteration over the collection elements. However, as a consequence, it’s also possible to alter the collection without the object knowing anything about it. For example, consider the following:

var collection:UIntCollection = new UIntCollection ();
collection.data[0] = "one";

In this example, you can see that a value is assigned to the data collection without the object being notified. Furthermore, the example assigns a string value to the collection element even though the UIntCollection class expects that the data collection contains only unsigned integers.

Note

ActionScript does not currently support typed arrays, which is why the preceding example allows us to assign a string to an element of the array. In a language with typed arrays, you would have to declare an array of a particular type (for example, uint).

The second flaw in the preceding solution is that it exposes not only the data, but also the structure of the data. If you want to iterate over the elements of a collection, there’s no reason you have to know the structure of the collection to accomplish that. In fact, having to know the structure is a hindrance because it requires different ways to iterate over different structures.

A second dilemma with iterating over collection data deals with the interface. In the preceding example, we noted that exposing collection data directly is not a good idea because it could have unexpected consequences. As a solution, it might seem like a good idea to define an API for the collection class that enables you to iterate over the collection data. For example, the following addition to the UIntCollection class enables you to loop through the elements of the collection while maintaining good encapsulation:

package com.peachpit.aas3wdp.collections {
   public class UIntCollection {
      private var _data:Array;
      private var _index:uint;

      public function UIntCollection() {
         _data = new Array();
         _index = 0;
      }

      public function addElement(value:uint):void {
         _data.push(value);
      }

      public function reset():void {
         _index = 0;
      }

      public function hasNext():Boolean {
         return _index < _data.length;
      }

      public function next():uint {
         return uint(_data[_index++]);
      }

   }
}

The downside with the preceding API is that it makes the collection responsible for iterating over the data. There are at least two major flaws with that: One flaw is that each collection object maintains its own cursor (_index) so that you cannot iterate over the collection object’s data more than once simultaneously. The second flaw is that the collection class has to define every possible way to iterate over the data. The preceding example assumes that you always want to iterate over the data in a forward direction, one element at a time, in the order that elements appear in the array. If you want to add methods for iterating over the collection in ascending value order or skipping every other element, you must add a lot of responsibilities to the collection class itself.

Understanding Iterator Elements

The Iterator pattern is a solution that enables you to iterate over a collection’s elements while maintaining good encapsulation and not having to expose the structure of the data. The Iterator pattern offloads the iteration responsibilities to a new object, and as a consequence, the collection class remains simple, and you can iterate over the collection more than once simultaneously.

The Iterator pattern consists of the following elements:

  • Iterator interface: The interface for iterating over the collection data

  • Concrete iterator: The implementation of the iterator interface

  • Collection interface: The interface that defines how to retrieve an iterator

  • Concrete collection: The implementation of the collection interface

The Iterator Interface

To define an iterator interface, you have to determine how much you want the iterator to be able to do. Initially it may seem important to define an interface that allows for moving forward or backward through the collection data. However, in practice, you generally don’t need the ability to move both forward and backward through the collection data. Rather, you generally want to move through the data in only one direction, one element at a time. The ability to reset an iterator back to the start is also useful. The following interface accomplishes these goals, and it is the one that we use throughout this book.

package com.peachpit.aas3wdp.iterators {
   public interface IIterator {

      function reset():void;
      function next():Object;
      function hasNext():Boolean;

   }
}

The preceding interface allows you to iterate over a collection, one element at a time. The reset() method simply moves the cursor back to the start of the collection data. The next() method returns the next element and advances the cursor. The hasNext() method returns true if there is a next element and false if there is no next element. And the current() method returns the current element without advancing the cursor.

Note

It’s important to understand that the IIterator interface is merely an interface. It does not dictate implementation. Although we might be accustomed to thinking of the word next as meaning moving forward, the next() method of an implementing class can just as easily move backward through a collection. For that matter an implementing class could skip every other element or even return random elements. The interface simply specifies what methods an implementing class must define.

The Concrete Iterator

The interface simply determines what methods the concrete iterator must implement. The concrete iterator defines the actual functionality. Perhaps one of the most common types of iterators for ActionScript is an iterator that can iterate over an array one element at a time starting with index 0. The following ArrayIterator definition accomplishes just that:

package com.peachpit.aas3wdp.iterators {
   public class ArrayIterator implements IIterator {
      
      private var _index:uint = 0;
      private var _collection:Array;

      public function ArrayIterator(collection:Array) {
         _collection = collection;
         _index = 0;
      }

      public function hasNext():Boolean {
         return _index < _collection.length;
      }

      public function next():Object {
         return _collection[_index++];
      }

      public function reset():void {
         _index = 0;
      }

   }
}

Of course, you can define many types of iterators depending on the collection data structures and the way in which you want to iterate over the data. The preceding example iterates over an array one element at a time with increasing indices. You could also define an iterator that returns the elements in reverse order, like this:

package com.peachpit.aas3wdp.iterators {
   public class ArrayReverseIterator implements IIterator {

      private var _index:uint = 0;
      private var _collection:Array;

      public function ArrayIterator(collection:Array) {
         _collection = collection;
         _index = _collection.length - 1;
      }

      public function hasNext():Boolean {
         return _index >= 0;
      }

      public function next():Object {
         return _collection[_index--];
      }

      public function reset():void {
         _index = _collection.length - 1;
      }

   }
}

Obviously, these are just two of the many types of iterators. Every iterator can define its own unique implementation. What is critical is that every iterator implements the same interface. The different implementations of the IIterator interface might allow access to different types of collections (associative arrays, for example). However, even if the collections are different, the iterator interface is the same, meaning you can access the data in the same way.

The Collection Interface

The collection interface defines the way in which you can access the iterator for a collection. The simplest interface is as follows:

package com.peachpit.aas3wdp.collections {
   public interface ICollection {
      unction iterator():IIterator;
   }
}

However, consider that you might want to enable many types of iterators for a collection. For example, you might want to allow a collection to return an iterator that advances one element at a time forward through the collection, or you might want to return an iterator that skips every other element in ascending order. For that reason, it is advantageous to define the interface as follows:

package com.peachpit.aas3wdp.collections {
   public interface ICollection {
      function iterator(type:String = null):IIterator;
   }
}

The implementing collection class can then return a different iterator type depending on the parameter passed to the method. The UIntCollection example in the next section illustrates this.

The Concrete Collection

The concrete collection class implements the collection interface. The following example rewrites the UIntCollection class so that it implements ICollection:

package com.peachpit.aas3wdp.collections {
   import com.peachpit.aas3wdp.IIterator;
   import com.peachpit.aas3wdp.ArrayIterator;
   public class UIntCollection implements ICollection {
      private var _data:Array;

      public function UIntCollection() {
         _data = new Array();
      }

      public function addElement(value:uint):void {
         _data.push(value);
      }

      public function iterator(type:String = null):IIterator {
         return new ArrayIterator(_data);
      }

   }
}

The preceding implementation returns only one type of iterator. However, if appropriate, you could enable several types of iterators, and the iterator that is returned would depend on the parameter value, as in the following example:

public function iterator(type:String = null):IIterator {
   if(type == "ArrayReverseIterator") {
      return new ArrayReverseIterator(_data);
   }
   else {
      new ArrayIterator(_data);
   }
}

In this example the user can create one UIntCollection object, and from the same interface she can request several different types of iterators.

var collection:UIntCollection = new UIntCollection();
collection.addElement(1);
collection.addElement(20);
collection.addElement(5);
collection.addElement(15);
var iteratorAscending:IIterator = collection.iterator();
var iteratorDescending:IIterator = collection.iterator("ArrayReverseIterator");

Using Iterators

After you’ve defined iterators and collections, you can use the iterators as in the following example, which adds four elements to a UIntCollection object and then uses an iterator to access that data:

var collection:UIntCollection = new UIntCollection();
collection.addElement(1);
collection.addElement(20);
collection.addElement(5);
collection.addElement(15);
var iterator:IIterator = collection.iterator();
while(iterator.hasNext()) {
   trace(iterator.next());
}

The preceding example uses the iterator to loop through each of the elements of the collection and write it to the debug console. Notice that because the iterator interface is identical for all iterator types, it doesn’t matter what concrete type the iterator() method returns. For example, the preceding code would continue to function correctly if you specified ArrayReverseIterator as the parameter for iterator(), thus returning an ArrayReverseIterator instead of an ArrayIterator. The only difference would be the output—which would return the collection values in reverse order.

Consider how different this approach is from using the array directly. When you allow direct access to the array, not only do you break encapsulation, you also create code that is specific to the implementation. For example, if you wanted to use a for statement to loop through the elements of an array directly, you would have to change the for statement expressions when you wanted to change how you loop through the array. But when you use the iterators, you don’t have to make such changes because the implementation is within the iterator itself.

Using Null Iterators

One special iterator type is the null iterator. The null iterator enables you to build classes that adhere to an interface (they are iterable) but that don’t actually have any collection data. The classic case for null iterators is the leaf element of a composite object (as discussed in Chapter 8, “Composite Pattern”). In such cases, it’s necessary that the leaf elements (those containing no collection data) and the composite elements (those that do contain collection data) implement the same interface and be treated in the same way. For recursive traversal purposes, it’s necessary that the interface provide access to an iterator for both composite and leaf elements. One option is to return null for the leaf element iterator. However, doing so presents a special case that you must detect—namely, you must add if statements to test whether the iterator is null or not before calling the methods such as hasNext() and next(). A more elegant solution is to use a special type of iterator that always returns false for hasNext() and returns null for next(). That special-case iterator is a null iterator as defined in the following code:

package com.peachpit.aas3wdp.iterators {
   public class NullIterator implements IIterator {
         
      public function NullIterator() {}
      public function hasNext():Boolean {
         return false;
      }
 
      public function next():Object {
         return null;
      }
         
      public function reset():void {
      }
        
   }
}

We’ll see an example of how to use this iterator type in the next chapter.

Summary

The Iterator pattern enables client code to read a collection’s data without exposing the structure of the data or making the data inadvertently writable. The Iterator pattern is a common pattern that provides a standard interface for reading collection data.

Although the Iterator pattern is simple, it is very useful. In fact, it is the pattern’s simplicity that makes it so useful. Because the pattern standardizes the way to access data from collections, it allows you to create interfaces that interact with the IIterator interface rather than with any specific implementation. That means that your code is more flexible and adaptable.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.17.204.7