今更デザパタ:Iteratorパターン

Rubyで書いていきます。RubyにはIteratorがすでにあるという話はおいといて。

テスト駆動で行きましょう。まずはテストケース test_iterator.rb

[]require[] []'[][]test/unit[][]'[]
[]require[] []'[][]iterator[][]'[]

[]class [][]TestIterator[] []<[] []Test[][]::[][]Unit[][]::[][]TestCase[]
  []def [][]setup[]
    []@bookshelf[] []=[] []Bookshelf[][].[][]new[]
    []@bookshelf[][].[][]append_book[][]([][]Book[][].[][]new[][]("[][]AWDwR[][]")[] [])[]
    []@bookshelf[][].[][]append_book[][]([][]Book[][].[][]new[][]("[][]RailsRecipes[][]")[] [])[]
    []@bookshelf[][].[][]append_book[][]([][]Book[][].[][]new[][]("[][]GettingReal[][]")[] [])[]
    []@bookshelf[][].[][]append_book[][]([][]Book[][].[][]new[][]("[][]AjaxInAction[][]")[] [])[]
    []@bookshelf[][].[][]append_book[][]([][]Book[][].[][]new[][]("[][]RideOnRails[][]")[] [])[]
  []end[]
  
  []def [][]test_book[]
    []assert_equal[][]("[][]test book[][]",[] []Book[][].[][]new[][]("[][]test book[][]").[][]name[][])[]
    []assert_not_equal[][]("[][]test book[][]",[] []Book[][].[][]new[][]("[][]test book?[][]").[][]name[][])[]
  []end[]
  
  []def [][]test_bookshelf[]
    []assert_equal[][]("[][]AWDwR[][]",[] []@bookshelf[][].[][]get_book_at[][]([][]0[][]).[][]name[][])[]
    []assert_equal[][]("[][]AjaxInAction[][]",[] []@bookshelf[][].[][]get_book_at[][]([][]3[][]).[][]name[][])[]
    []assert_kind_of[][]([][]Numeric[][],[] []@bookshelf[][].[][]length[][])[]
    []assert_equal[][]([][]nil[][],[] []@bookshelf[][].[][]get_book_at[][]([][]@bookshelf[][].[][]length[][]))[]
    []assert_kind_of[][]([][]Iterator[][],[] []@bookshelf[][].[][]iterator[][])[]
  []end[]
  
  []def [][]test_iterator[]
    []iterator[] []=[] []@bookshelf[][].[][]iterator[]
    []while[][]([][]iterator[][].[][]has_next?[][])[]
      []assert_nothing_raised[][]{[] []iterator[][].[][]next[][].[][]name[] []}[]
    []end[]
    []assert_not_equal[][]([][]true[][],[] []iterator[][].[][]has_next?[][])[]
    []assert_equal[][]([][]nil[][],[] []iterator[][].[][]next[][])[]
  []end[]
[]end[]

続いて、実装。iterator.rb

[]class [][]Aggregate[]
  []def [][]iterator[]
    []raise[] []NotImplementedError[][].[][]new[]
  []end[]
[]end[]

[]class [][]Iterator[]
  []def [][]has_next?[]
    []raise[] []NotImplementedError[][].[][]new[]
  []end[]
  
  []def [][]next[]
    []raise[] []NotImplementedError[][].[][]new[]
  []end[]
[]end[]

[]class [][]Bookshelf[] []<[] []Aggregate[]
  []def [][]initialize[]
    []@books[] []=[] []Array[][].[][]new[]
  []end[]
  
  []def [][]get_book_at[][]([][]index[][])[]
    []@books[][][[][]index[][]][]
  []end[]
  
  []def [][]append_book[][]([][]book[][])[]
    []unless[] []Book[] []===[] []book[]
      []raise[] []TypeError[]
    []end[]
    
    []@books[] []<<[] []book[]
  []end[]
  
  []def [][]iterator[]
    []BookshelfIterator[][].[][]new[][]([][]self[][])[]
  []end[]
  
  []def [][]length[]
    []@books[][].[][]length[]
  []end[]
[]end[]

[]class [][]Book[]
  []attr_accessor[] []:name[]
  
  []def [][]initialize[][]([][]name[][])[]
    []@name[] []=[] []name[]
  []end[]
[]end[]

[]class [][]BookshelfIterator[] []<[] []Iterator[]
  []def [][]initialize[][]([][]bookshelf[][])[]
    []@index[] []=[] []0[]
    []@bookshelf[] []=[] []bookshelf[]
  []end[]
  
  []def [][]has_next?[]
    []if[] []@index[] []<[] []@bookshelf[][].[][]length[]
      []true[]
    []else[]
      []false[]
    []end[]
  []end[]
  
  []def [][]next[]
    []ret[] []=[] []@bookshelf[][].[][]get_book_at[][]([][]@index[][])[]
    []@index[] []+=[] []1[]
    []return[] []ret[]
  []end[]
[]end[]

IteratorとAggregateはインターフェースとして使いたいので、それぞれのメソッドにはraise NotImplementedError.newしておく。

こういうのを作ると、Rubyのブロックのありがたさがわかるなぁ。