Iterator パターン

id:hyuki さんのデザインパターン本の Iterator パターンを Ruby に移植してみます。

#!/usr/local/bin/ruby

class Iterator
  def has_next
  end
  def next
  end
end

class Aggregate
  def iterator
  end
end

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

class BookShelf < Aggregate
  def initialize
    @books = Array.new
    @last = 0
  end
  
  def [](index)
    @books[index]
  end

  def append_book(book)
    @books[@last] = book
    @last += 1
  end

  def length
    @last
  end

  def iterator
    BookShelfIterator.new(self)
  end
end

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

  def has_next
    if (@index < @bookshelf.length)
      return true
    else
      return false
    end
  end

  def next
    book = @bookshelf[@index]
    @index += 1
    return book
  end
end

shelf = BookShelf.new
shelf.append_book(Book.new("Around the World in 80 Days"))
shelf.append_book(Book.new("Bible"))
shelf.append_book(Book.new("Cinderella"))
shelf.append_book(Book.new("Daddy-Long-Legs"))

it = shelf.iterator
while (it.has_next) 
  book = it.next
  puts book.name
end

これを実行すると

$ ruby iterator.rb
Around the World in 80 Days
Bible
Cinderella
Daddy-Long-Legs

となります。

Java のコードをほぼそのままで getBookAt(int index)[](index) にしてるところが違うぐらいのコードです。でもこれですと、

  • Iterator クラス、Aggregate クラスはコードを実行する上では全く意味を為していない
    • Java の場合は interface として書けるので意味がある
  • while 文でイテレータを回すあたりが ruby らしくない

という感じがします。そこで http://www.freeml.com/message/patterns/997 で高橋さんが書いたコードのように each を使うようにしてみます。

#!/usr/local/bin/ruby

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

class BookShelf
  def initialize
    @books = Array.new
  end
  
  def append_book(book)
    @books.push(book)
  end

  def each
    @books.each do |book|
      yield(book)
    end
  end
end

shelf = BookShelf.new
shelf.append_book(Book.new("Around the World in 80 Days"))
shelf.append_book(Book.new("Bible"))
shelf.append_book(Book.new("Cinderella"))
shelf.append_book(Book.new("Daddy-Long-Legs"))

shelf.each do |book|
  puts book.name
end

shelf.each do |book| ... end で回せるようになって ruby らしいコードになりました。BookShelfIterator として機能していた箇所は yield を使った each の中に内包されたので、クラスは二つだけになりました。

高橋さんのコードの最終形にもあるとおり、この場合の BookShelf は Array を継承するだけでも良かったりします。ただし、その場合 BookShelf がデータ構造を抽象化していることにはならないのかもしれません。

yield がすぐに頭に出てくるようになるにはもっと修行が必要そうです。

増補改訂版Java言語で学ぶデザインパターン入門

増補改訂版Java言語で学ぶデザインパターン入門