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.
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.
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
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
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.
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.
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
Data modification. The 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.
Privileged access. Enumeration 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.
External versus Internal Iterators. The 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.