External Iterators

~ 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

Internal Iterators

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

Internal Iterators versus External Iterators

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.

Using and Abusing the Iterator Pattern

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