Design Patterns: Ruby Companion

4 Iterator Pattern

4.1 Intent

Provide a way to access the elements of a aggregate object without exposing its underlying representation.

4.2 Implementation of Iterator Pattern

4.2.1 Sample 1: normal implementation

# Iterator Pattern
# sample1

class Book
  def initialize(name)
    @name = name
  end

  attr_reader :name
end

class BookShelf
  def initialize()
    @books = Array.new()
  end
  def [](index)
    return @books[index]
  end
  def append_book(book)
    @books << book
  end
  def length
    return @books.length
  end
  def iterator
    return BookShelfIterator.new(self)
  end
end

class BookShelfIterator
  def initialize(bookshelf)
    @bookShelf = bookshelf
    @index = 0
  end

  def has_next?()
    @index < @bookShelf.length
  end

  def next()
    book = @bookShelf[@index]
    @index += 1
    return book
  end
end

### main

if __FILE__ == $0
  bookShelf = BookShelf.new()
  bookShelf.append_book(Book.new("Around the World in 80 Days"))
  bookShelf.append_book(Book.new("Bible"))
  bookShelf.append_book(Book.new("Cinderella"))
  bookShelf.append_book(Book.new("Daddy-Long-Legs"))

  it = bookShelf.iterator()

  while it.has_next?
    book = it.next()
    print book.name, "\n"
  end
end

4.2.2 Sample 2: internal iterator

In sample 1, there is a two objects: a container and a iterator which scan container. When you iterate, you need to call next method.

But in Ruby, we usually use iterator without next method. Ruby's iterator is called 'internal iterator'. It is a method that the container has. Iterator like sample 1 is called 'external iterator'.

A sample of internal iterator is bellow. each method is used as iterator method.

# Iterator Pattern
# sample2: using Enumerable

class Book
  def initialize(name)
    @name = name
  end

  attr_reader :name
end

class BookShelf
  include Enumerable

  def initialize()
    @books = Array.new()
  end

  def [](index)
    @books[index]
  end

  def <<(book)
    @books << book
  end

  def length()
    @books.length
  end

  def each()
    @books.each{|book|
      yield(book)
    }
  end
end

## main

if __FILE__ == $0
  bookShelf = BookShelf.new()
  bookShelf << Book.new("Around the World in 80 Days")
  bookShelf << Book.new("Bible")
  bookShelf << Book.new("Cinderella")
  bookShelf << Book.new("Daddy-Long-Legs")

  bookShelf.each{|book|
    print book.name, "\n"
  }

  bookstr =  bookShelf.collect{|book|
    '"'+book.name+'"'
  }.join(":")
  print bookstr, "\n"
end

4.2.3 Sample 3: Struct and Inheritance of Array

The definition of BookShelf class in sample 2 tell me BookShelf class has almost same functions with Array class. So, you can use Array class instead of BookShelf. But when you use it you cannot extend container class without influence to other class using Array. So let's use Inherited class of Array.

# Iterator Pattern
# sample3: using Struct and inheretance of Array class

## Book class
Book = Struct.new("Book", :name)

## BookShelf class
class BookShelf < Array; end

## main
if __FILE__ == $0
  bookShelf = BookShelf.new()
  bookShelf << Book.new("Around the World in 80 Days")
  bookShelf << Book.new("Bible")
  bookShelf << Book.new("Cinderella")
  bookShelf << Book.new("Daddy-Long-Legs")

  bookShelf.each{|book|
    print book.name, "\n"
  }

  bookstr =  bookShelf.collect{|book|
    '"'+book.name+'"'
  }.join(":")
  print bookstr, "\n"
end

Struct class makes classes with attributes. This classes have attributes and access methods(setter and getter) to the attributes. Notice that instance of Struct is a new class itself.

4.2.4 Sample 4: delegating to Array

In this sample, we use delegation instead of inheritance. With deletation, we can use other class' functions without inheritance.

# Iterator Pattern
# sample4: using delegate 

require 'delegate'

## Book class
Book = Struct.new("Book", :name)

## BookShelf class
class BookShelf < DelegateClass(Array)
  def initialize()
    super([])
  end
end


## main
if __FILE__ == $0
  bookShelf = BookShelf.new()
  bookShelf << Book.new("Around the World in 80 Days")
  bookShelf << Book.new("Bible")
  bookShelf << Book.new("Cinderella")
  bookShelf << Book.new("Daddy-Long-Legs")

  bookShelf.each{|book|
    print book.name, "\n"
  }

  bookstr =  bookShelf.collect{|book|
    '"'+book.name+'"'
  }.join(":")
  print bookstr, "\n"
end

4.2.5 Sample 5: forwarding to Array

Deletagor class delegages (almost) all methods of original class.

When you delegetes a few methods, you can use forwardable library. Forwardable class delegates only specified methods.

# Iterator Pattern
# sample5: using forwarding

require 'forwardable'

## Book class
Book = Struct.new("Book", :name)

## BookShelf class
class BookShelf
  extend Forwardable
  def_delegators("@books", "<<", "[]", "length", "each")

  include Enumerable

  def initialize()
    @books = Array.new()
  end
end


## main
if __FILE__ == $0
  bookShelf = BookShelf.new()
  bookShelf << Book.new("Around the World in 80 Days")
  bookShelf << Book.new("Bible")
  bookShelf << Book.new("Cinderella")
  bookShelf << Book.new("Daddy-Long-Legs")

  bookShelf.each{|book|
    print book.name, "\n"
  }

  bookstr =  bookShelf.collect{|book|
    '"'+book.name+'"'
  }.join(":")
  print bookstr, "\n"
end

4.3 Sources

<URL:src-iterator.tar.gz>

4.3.1 LICENSE

Original version of this sources are written by YUKI Hiroshi-san in "Introduction to Design Patterns with Java", and modified totally by TAKAHASHI Masayoshi to use Ruby.

License of Origanal sources is <URL:http://www.hyuki.com/dp/index.html#download> (in Japanese and English). This modified version is under the same license.

Author: TAKAHASHI 'Maki' Masayoshi (maki@rubycolor.org)