Chapter 25. The Iterator Pattern

The Iterator is one of the simplest and most frequently used of the design patterns. The Iterator pattern allows you to move through a list or collection of data using a standard interface without having to know the details of the internal representations of that data. In addition, you can also define special iterators that perform some special processing and return only specified elements of the data collection.

Motivation

The Iterator is useful because it provides a defined way to move through a set of data elements without exposing what is taking place inside the class. Since the Iterator is an interface, you can implement it in any way that is convenient for the data you are returning. Design Patterns suggests that a suitable interface for an Iterator might be the following.

Public Interface Iterator
       public Function First() as Object
       public Function  Next() as Object
       public Function isDone() as Boolean
       public Function CurrentItem() as Object
End Interface

Here you can move to the top of the list, move through the list, find out if there are more elements, and find the current list item. This interface is easy to implement and it has certain advantages, but a number of other similar interfaces are possible. For example, when we discussed the Composite pattern, we introduced the Subords class for looping through all of the subordinates any employee may have. The interface we used can be reduced in VB7 terms to the following.

Public Interface Iterator
  Public Sub moveFirst()
  Public Function hasMoreElements() as Boolean
  Public Function nextElement() as Object
End Interface

This also allows us to loop through a list of zero or more elements in some internal list structure without our having to know how that list is organized inside the class.

One disadvantage of this Enumeration over similar constructs in C++ and Smalltalk is the strong typing of the VB7 language. This prevents the hasMoreElements() method from returning an object of the actual type of the data in the collection. Instead, you must convert the returned Object type to the actual type of the data in the collection. Thus, while the Iterator or Enumeration interface is intended to be polymorphic, this is not directly possible in VB7.

Sample VB6 Code

Let's reuse the list of swimmers, clubs, and times we described in Chapter 22, and add some enumeration capabilities to the KidData class. This class is essentially a collection of Kids, each with a name, club, and time, and these Kid objects are stored in a Collection.

'Class Kids
Implements Iterator
Private kidList As Collection
Private index As Integer

Public Sub init(Filename As String)
 Dim sline As String     'line read in
 Dim vbf As New vbFile   'file class
 Dim kd As Kid           'kid object
 Set kidList = New Collection
 vbf.OpenForRead Filename    'open the file
 While Not vbf.fEof          'read in the lines
   sline = vbf.readLine
   Set kd = New Kid
   kd.init sline             'convert to kid
   kidList.Add kd            'Add to collection
 Wend
 vbf.closeFile
 Iterator_moveFirst      'move to top of list
End Sub

To obtain an enumeration of all the Kids in the collection, we simply use the methods of the Iterator interface we just defined.

Private Function Iterator_hasMoreElements() As Boolean
 Iterator_hasMoreElements = index < kidList.Count
End Function
'-----
Private Sub Iterator_moveFirst()
 index = 1
End Sub
'-----
Private Function Iterator_nextElement() As Object
 Set Iterator_nextElement = kidList(index)
 index = index + 1
End Function

Reading in the data and displaying a list of names is quite easy. We initialize the Kids class with the filename and have it build the collection of kid objects. Then we treat the Kids class as an instance of Iterator and move through it to get out the kids and display their names.

'Class KidForm
Dim kidz As Kids        'same as iter

Private Sub Form_Load()
 Dim iter As Iterator    'same as kidz
 Dim kd As Kid

 Set kidz = New Kids
 'initialize the collection class
 'and read in the data file
 kidz.init App.Path & "50free.txt"

 'treat collection class as iterator
 Set iter = kidz
 While iter.hasMoreElements  'load into listbox
   Set kd = iter.nextElement
   List1.AddItem kd.getFrname & " " & kd.getLname
  Wend
End Sub

Fetching an Iterator

Another slightly more flexible way to handle iterators in a class is to provide the class with a getIterator method that returns instances of an iterator for that class's data. This is somewhat more flexible because you can have any number of iterators active simultaneously on the same data. Our KidIterator class can then be the one that implements our Iterator interface.

'Class KidIterator
Implements Iterator
Private index As Integer
Private kidList As Collection
'-----
Public Sub init(col As Collection)
 index = 1
 Set kidList = col
End Sub
'-----
Private Function Iterator_hasMoreElements() As Boolean
 Iterator_hasMoreElements = index < kidList.Count
End Function
'-----
Private Sub Iterator_moveFirst()
 index = 1
End Sub
'-----
Private Function Iterator_nextElement() As Object
 Set Iterator_nextElement = kidList(index)
 index = index + 1
End Function

We can fetch iterators from the main KidList class by creating them as needed.

Public Function getIterator() As Iterator
 Dim kiter As New KidIterator    'create an iterator
  kiter.init kidList             'initialize it
  Set getIterator = kiter        'and return it
End Function

Filtered Iterators

While having a clearly defined method of moving through a collection is helpful, you can also define filtered Iterators that perform some computation on the data before returning it. For example, you could return the data ordered in some particular way or only those objects that match a particular criterion. Then, rather than have a lot of very similar interfaces for these filtered iterators, you simply provide a method that returns each type of enumeration with each one of these enumerations having the same methods.

The Filtered Iterator

Suppose, however, that we wanted to enumerate only those kids who belonged to a certain club. This necessitates a special Iterator class that has access to the data in the KidData class. This is very simple because the methods we just defined give us that access. Then we only need to write an Iterator that returns kids belonging to a specified club.

'Class KidClubIterator
Implements Iterator
Private index As Integer
Private kidList As Collection
Private club As String
'-----
Public Sub init(col As Collection, clb As String)
 index = 1
 Set kidList = col
 club = clb
End Sub
'-----
Private Function Iterator_hasMoreElements() As Boolean
 Dim more As Boolean
 Dim kd As Kid
 more = index <= kidList.Count
 If more Then
   Set kd = kidList(index)
   While more And kd.getClub <> club
     Set kd = kidList(index)
     index = index + 1
     more = index <= kidList.Count
   Wend
 End If
 Iterator_hasMoreElements = more
End Function
'-----
Private Sub Iterator_moveFirst()
index = 1
End Sub
'-----
Private Function Iterator_nextElement() As Object
 Set Iterator_nextElement = kidList(index)
 index = index + 1
End Function

All of the work is done in the hasMoreElements() method, which scans through the collection for another kid belonging to the club specified in the constructor and saves that kid in the kid variable or sets it to null. Then it returns either true or false. The nextElement() method returns that next kid variable.

Finally, we need to add a method to KidData to return this new filtered Enumeration.

Public Function getClubIterator(clb As String) As Iterator
Dim kiter As New KidClubIterator    'create an iterator
 kiter.init kidList, clb            'initialize it
 Set getClubIterator = kiter        'and return it
End Function

This simple method passes the collection to the new Iterator class kid-ClubIterator along with the club initials. A simple program is shown in Figure 25-1 that displays all of the kids on the left side. It fills a combo box with a list of the clubs and then allows the user to select a club and fills the right-hand list box with those belonging to a single club. The class diagram is shown in Figure 25-2. Note that the elements method in KidData supplies an Enumeration and the kidClub class is in fact itself an Enumeration class.

A simple program illustrating filtered enumeration

Figure 25-1. A simple program illustrating filtered enumeration

The classes used in the Filtered enumeration

Figure 25-2. The classes used in the Filtered enumeration

Iterators in VB.NET

You can write virtually the same code in VB7 as you could in VB6 with the slight changes in how interfaces are declared. For example, the KidClubIterator we wrote for VB6 varies in VB7 only in that we change Collections to ArrayLists, change to zero-based arrays, use constructors instead of init methods, and simplify the code slightly, using the return statement. Here is the revised KidClubIterator class.

Public Class KidClubIterator
    Implements Iterator
    Private index As Integer
    Private kidList As arraylist
    Private club As String
    '-----
    Public Sub New(ByRef col As ArrayList, _
            ByRef clb As String)
        MyBase.New()
        index = 0
        kidList = col
        club = clb
    End Sub
    '-----
    Public Function hasMoreElements() As Boolean _
             Implements Iterator.hasMoreElements
        Dim more As Boolean
        Dim kd As Kid
        more = index < kidList.Count()
        If more Then
            kd = CType(kidList.Item(index), kid)
            While more And kd.getClub <> club
                kd = CType(kidList.Item(index), kid)
                index = index + 1
                more = index < kidList.Count()
            End While
        End If
        Return more
    End Function
    '-----
    Public Sub moveFirst() Implements Iterator.moveFirst
        index = 0
    End Sub
    '-----
    Private Function nextElement() As Object _
        Implements Iterator.nextElement
        index = index + 1
        Return kidList.Item(index - 1)
    End Function
End Class

However, you can also write iterators using the standard VB.Net IEnumerator interface, which amounts to the following.

Function MoveNext() as Boolean
Sub Reset()
Property ReadOnly Current as Object

If you rewrite your KidIterator class to use these methods, the code looks like this.

    Private index As Integer
    Private kidList As ArrayList
    '-----
    Public Sub New(ByVal col As Arraylist)
        index = 0
        kidList = col
    End Sub
    '-----
    Public Function MoveNext() As Boolean _
        Implements IEnumerator.MoveNext
        index = index + 1
        Return index < kidList.Count
    End Function
    '-----
    Public Sub Reset() Implements _
            IEnumerator.Reset
        index = 0
    End Sub
    '-----
    Public ReadOnly Property Current() As Object _
        Implements IEnumerator.Current
        Get
            Return kidList.Item(index)
        End Get
    End Property
End Class

If you have a class like our Kids class that can return an instance of an enumerator, it is said to implement the IEnumerable interface.

Public Function GetEnumerator() As IEnumerator _
Implements IEnumerable.GetEnumerator

Consequences of the Iterator Pattern

  1. Data modificationThe most significant question iterators may raise is the question of iterating through data while it is being changed. If your code is wide ranging and only occasionally moves to the next element, it is possible that an element might be added or deleted from the underlying collection while you are moving through it. It is also possible that another thread could change the collection. There are no simple answers to this problem. If you want to move through a loop using an Enumeration and delete certain items, you must be careful of the consequences. Deleting or adding an element might mean that a particular element is skipped or accessed twice, depending on the storage mechanism you are using.

  2. Privileged accessEnumeration classes may need to have some sort of privileged access to the underlying data structures of the original container class so they can move through the data. If the data is stored in an Arraylist or Hashtable, this is pretty easy to accomplish, but if it is in some other collection structure contained in a class, you probably have to make that structure available through a get operation. Alternatively, you could make the Iterator a derived class of the containment class and access the data directly.

  3. External versus Internal IteratorsThe Design Patterns text describes two types of iterators: external and internal. Thus far, we have only described external iterators. Internal iterators are methods that move through the entire collection, performing some operation on each element directly without any specific requests from the user. These are less common in VB, but you could imagine methods that normalized a collection of data values to lie between 0 and 1 or converted all of the strings to a particular case. In general, external iterators give you more control because the calling program accesses each element directly and can decide whether to perform an operation on it.

Programs on the CD-ROM

IteratorSimpleIter VB6 kid list using Iterator
IteratorFilteredIterator VB6 filtered iterator by team name
IteratorFilteredIteratorVBNet VB7 filtered iterator
..................Content has been hidden....................

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