~ We can loop through the array
from the element (i) (but no need to know about the array
)
The GoF tell us that the Iterator pattern will do the following:
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation. (
array
)
As a free bonus, Ruby’s flexible dynamic typing allows ArrayIterator
to work on any aggregate class that has a length
method and can be indexed by an integer. String is just such a class, and our ArrayIterator will work fine with strings:
i = ArrayIterator.new('abc')
while i.has_next?
puts("item: #{i.next_item.chr}")
end
Building an internal iterator for arrays is very easy—we just define a method that calls (via yield) the passed-in code block for each element:
def for_each_element(array) i= 0
while i < array.length
yield(array[i])
i += 1
end
end
With an external iterator, you won’t call next until you are good and ready for the next element. With an internal iterator, by contrast, the aggregate relentlessly pushes the code block to accept item after item.
While Iterator is one of the most commonly used and useful patterns, it does have some pointy bits sticking out waiting to snag the unwary. The main danger is this: What happens if the aggregate object changes while you are iterating through it?
Suppose you are sequencing through a list and just before you get to the third element, someone deletes that element from the list. What happens? Does the iterator show you the now-defunct third element anyway? Does the iterator quietly go on to the fourth element as though nothing has happened? Does it throw an exception?
We can make our ArrayIterator resistant to changes to the underlying array by simply making a copy
of the array in the iterator constructor:
class ChangeResistantArrayIterator
def initialize(array)
@array = Array.new(array)
@index = 0
end
end
def change_resistant_for_each_element(array)
copy = Array.new(array)
i = 0
while i < copy.length
yield(copy[i])
i += 1
end
end