Design Pattern: Ruby Companion
(ずばっと略)
Ruby版では,全てのクラス定義とサンプルを1つのソースにまとめています.
素朴にRubyで実装したものです.メソッドなどはRubyっぽく変えたところもあります.
# 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
「サンプルその1」は,オブジェクトを格納するコンテナと,コンテナをスキャンするイテレータとが別々のオブジェクトになっていて,イテレータを前に進ませるにはその都度 next メソッドを実行させる必要がありました.
ところで,Rubyで「イテレータ」というと,ふつうはサンプルその1のようなものではなく,コンテナとイテレータとが同じオブジェクトになっているものを指します.そして,イテレータを進ませるためのメソッドは必要ありません.イテレータに勝手に進ませるのです.このようなイテレータを,「内部イテレータ」と言います.これに対して,サンプル1のようなイテレータを「外部イテレータ」とも言います.
Rubyの内部イテレータを使って実装すると,以下のようになります.イテレータはeachメソッドによって実装されています.
# 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
サンプルその2のBookShelfクラスの定義の通り,BookShelfクラスは,結局のところほとんどArrayクラスと同じ機能を持つクラスになります.そのため,いっそのことArrayをそのまま使っても構わないのですが,そうすると今度はBookShelf独自のメソッドを定義できなくなります.そこで,Arrayを継承させたクラスにしてみましょう.
また,単なる属性を持ったオブジェクトのクラスがほしい場合には, Structクラスを使います.これは,単なる属性と,その属性へのアクセスメソッドのみのクラスを作るためのクラスです.Struct.newの結果を定数に代入するのがミソで,その定数が新しいクラスになります.
# 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
継承の代わりに委譲を使ってみましょう.委譲を使えば,クラスを継承させることなく,他のクラスの機能を利用できます.
# 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
Deletagorを使った場合,元のクラスの全てのメソッドについて委譲されてしまうようになります.しかし,そうではなく,限られたメソッドについてのみ委譲したいことも多々あります.
そのような場合には,forwardableライブラリを使います.こちらは,委譲したいメソッドを指定しておけば,そのメソッドのみ委譲されるようになります.
# 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
使い方が限定されている分には,外部イテレータよりも内部イテレータの方が便利でしょう.ただし,複雑な使い方をする場合には,内部イテレータで頑張るよりは外部イテレータを使い,自分で制御した方がシンプルになりそうです.
固めておきました.こちらからどうぞ.
このソースは,結城浩さんによる『Java言語で学ぶデザインパターン入門』を元に,たかはしが Ruby用に手を入れたものです.Rubyとして自然なソースにするようにしたため,あんまり原型を留めてません.
オリジナルのソースのライセンスは, <URL:http://www.hyuki.com/dp/index.html#download> にあります.このソースの扱いも上記と同様でお願いします.
文責: たかはし(maki@rubycolor.org)