Abstract Factory パターン
id:hyuki さんのデザインパターン本から Abstract Factory パターンを移植してみます。
#!/usr/local/bin/ruby class Item def initialize(caption) @caption = caption end end class Link < Item def initialize(caption, url) super(caption) @url = url end end class Tray < Item def initialize(caption) super(caption) @tray = Array.new end def add(item) @tray.push(item) end end class Page def initialize(title, author) @title = title @author = author @content = Array.new end def add(item) @content.push(item) end def output self.make_html end end class Factory def Factory.get_factory(classname) eval(classname).new end end class ListFactory < Factory def create_link(caption, url) ListLink.new(caption, url) end def create_tray(caption) ListTray.new(caption) end def create_page(title, author) ListPage.new(title, author) end end class ListLink < Link def make_html return "<li><a href=?"#@url?">#@caption</a></li>?n" end end class ListTray < Tray def make_html result = String.new result += "<li>#@caption?n" result += "<ul>?n" @tray.each do |item| result += item.make_html end result += "</ul>?n" result += "</li>" result end end class ListPage < Page def make_html list = @content.map{ |item| item.make_html }.join('') return <<EOS <html> <head><title>#@title</title></head> <body> <h1>#@title</h1> <ul> #{list} </ul> <hr><address>#@author</address> </body> </html> EOS end end if ARGV.size != 1 puts "Usage: #$0 <classname>" exit end factory = Factory.get_factory(ARGV.shift) hatena = factory.create_link("はてな", "http://www.hatena.ne.jp/") google = factory.create_link("Google", "http://www.google.com/") yahoo = factory.create_link("Yahoo!", "http://www.yahoo.com/") yahoo_jp = factory.create_link("Yahoo! Japan", "http://www.yahoo.co.jp/") yahoo_tray = factory.create_tray("Yahoo!") yahoo_tray.add(yahoo) yahoo_tray.add(yahoo_jp) page = factory.create_page("LinkPage", "るびお") page.add(hatena) page.add(google) page.add(yahoo_tray) puts page.output
このコードを、引数に "ListFactory" と与えて実行すると、
$ ruby abstract_factory.rb ListFactory <html> <head><title>LinkPage</title></head> <body> <h1>LinkPage</h1> <ul> <li><a href="http://www.hatena.ne.jp/">はてな</a></li> <li><a href="http://www.google.com/">Google</a></li> <li>Yahoo! <ul> <li><a href="http://www.yahoo.com/">Yahoo!</a></li> <li><a href="http://www.yahoo.co.jp/">Yahoo! Japan</a></li> </ul> </li> </ul> <hr><address>るびお</address> </body> </html>
となりました。んー、書籍の例は Java の例で、抽象クラスでサブクラスの実装に縛りをかけながら Factory でごっそりそれらを切り替えるというのがとても美しいのですが、縛りをかけられない Ruby ではいまいちしまりがない感じがします。Item や Tray はコンストラクタのみの実装ですし。
そういえば、一つ学びました。
class Factory def Factory.get_factory(classname) begin eval(classname).new rescue NameError puts "Unknown factory class: #{classname}." end end end
クラス名の文字列からインスタンスを生成する方法です。eval で文字列を評価してからメソッドを起動するとうまくいきました。
に高橋さんが実装したものを見つけました。"素朴な実装" のところが普通に移植したものです。
- eval ではなく Object.const_get をつかっている。const_get って何でしょう。
- ファイルを factory.rb / listfactory.rb / tablefactory.rb に分割している
- raise NotImplementedError を記述してる箇所がある
というところ以外はほとんど一緒の実装でした。その他に
というものがありました。参考になります。いまから熟読します。