Prototype パターン
id:hyuki さんのデザインパターン本から Prototype パターンを移植してみます。
#!/usr/local/bin/ruby class Manager def initialize @showcase = Hash.new end def register (name, proto) @showcase[name] = proto end def create(protoname) p = @showcase[protoname] p.create_clone end end class MessageBox def initialize(decochar) @decochar = decochar end def use(str) length = str.length (length + 4).times { print @decochar } puts "" puts "#@decochar #{str} #@decochar" (length + 4).times { print @decochar } puts "" end def create_clone self.dup end end manager = Manager.new manager.register('warning box', MessageBox.new('*')) manager.register('slash box', MessageBox.new('/')) [manager.create('warning box'), manager.create('slash box')].each do |product| product.use("Hello, World!") end
このスクリプトの実行結果は、
$ ruby prototype.rb ***************** * Hello, World! * ***************** ///////////////// / Hello, World! / /////////////////
となります。
書籍では Product というインタフェースが出てきて、MessageBox はその Product を継承しています。Product が MessageBox のインタフェースを規定します。一方の移植版は、Ruby なのでインタフェースのような概念がないため、このような実装になりました。
書籍ではもう一つ Underline という、Product を継承したクラスがでてきます。Product インタフェースによって MessageBox と Underline はどちらも同じインタフェースを持つことになり、また Manager クラスはそれらサブクラスに束縛されることなく Product インタフェースを扱うことで、結合が疎になるという例。
Ruby の場合はこの辺はダックタイピングで、ということになるかと思います。
ところで MessageBox の create_clone は移植のために便宜的に用意してますが、Ruby は Kernel#clone や Kernel#dup があるためすべてのオブジェクトが複製可能です。従ってこのメソッドは削除してしまって、Manager の create で直接 p.dup
してしまっても良さそうです。
すると Manager は
class Manager def initialize @showcase = Hash.new end def register (name, proto) @showcase[name] = proto end def create(protoname) p = @showcase[protoname] p.dup end end
となるわけですが、なんとなく Hash を中途半端に抽象化しているだけのクラスになってしまいました。いっそ Manager は Hash を継承させてしまって、create を追加したものでも良いかもしれません。
あるいは、Hash と同じインタフェースであるが、値を取り出すときはインスタンスのコピーが取り出されるというクラスにしてみるのも面白いかなと思います。これを以下のように実装してみました。
class PrototypeHash include Enumerable def initialize @internal = Hash.new end def each @internal.each do |key, value| yield(key, value) end end def []=(name, proto) @showcase[name] = proto end def [](proto) p = @showcase[proto] p.dup end end class MessageBox ... end manager = PrototypeHash.new manager[:warning] = MessageBox.new('*') manager[:slash] = MessageBox.new('/') [manager[:warning], manager[:slash]].each do |product| product.use("Hello, World!") end
うまく動きました。こういう実装の方が Ruby らしいような気がしますが、どうでしょうか。