Rubyによるデザインパターンまとめ : builder

Rubyによるデザインパターン

Rubyによるデザインパターン

利用する場面

オブジェクトを構成するために大量のコードが必要な場合。

方法

オブジェクトを構成するためのコードをオブジェクト自体から分離し、builderクラスにもたせる。

あれこれ

他のパターンとの違いについて

factoryは、決まりきった一式のオブジェクトを生成するときに適用する。(こっちのオブジェクトはa,b,cというインスタンスをつくり、こっちのオブジェクトはx,y,zというインスタンスをつくる、といった具合に。)

builderは、様々なパターンのオブジェクトを煩雑なオプションによって生成するときに適用する。(こっちのオブジェクトはa,b,cというインスタンスをつくり、こっちはa',b'',c,dをつくる、といった具合に。)

以下の例えが大いに理解を助けてくれたと感じた。

「ハーディーズ(Hardee’s: 米国のレストランチェーン)で何か注文するところを考えます。たとえば『ビッグハーディ』を頼めば、店の人は何も聞かずに持ってきます。これだとsimple factoryの例になります。しかしたとえば、少し凝ったサブウェイ風のが欲しい場合、ハンバーガーの作り方にはいろいろなオプションが考えられます(どんなバンズにするか、ソースの種類は何がいいか、チーズはどれにするか、など)。このような場合にはbuilderパターンが助けに来てくれるでしょう。」(参考:[保存版]人間が読んで理解できるデザインパターン解説#1: 作成系(翻訳)

また、書籍に記載されたサンプルコードでいえばだが、以下の点も違いとして見受けられた。

まあこれは本質的な違いではなく、サンプルコードがたまたまそうなったというだけの話で、どちらの方法もありうるのではないかと思う。

サンプル

class Jiro
  attr_accessor :abura, :yasai, :karame
end

class JiroBuilder
  attr_reader :jiro

  def initialize
    @jiro = Jiro.new
  end

  def add_abura(order = :hutsuu)
    @jiro.abura = Abura.new(order) 
  end

  def add_yasai(order = :hutsuu)
    @jiro.yasai = Yasai.new(order)
  end

  def add_karame(order = :hutsuu)
    @jiro.karame = Karame.new(order)
  end
end

class Topping
  def initialize(order)
    @volume = 
      case order
      when :mashimashi
        300
      when :mashi
        100
      when :sukuname
        20
      else :hutsuu
        50
      end
  end
end

class Abura < Topping  ; end
class Yasai < Topping  ; end
class Karame < Topping ; end

jb = JiroBuilder.new
jb.add_abura
jb.add_yasai(:mashimashi)
jb.add_karame(:sukuname)
jiro = jb.jiro
p jiro.abura  # => #<Abura:0x007fafea957120 @volume=50>
p jiro.yasai  # => #<Yasai:0x007fafea9570a8 @volume=300>
p jiro.karame # => #<Karame:0x007fafea957080 @volume=20>

method_missingを利用したmagic methodというものをやってみる。

JiroBuilderクラスに以下のメソッドを追加する。

  def method_missing(name, *args)
    words = name.to_s.split("_")
    words.each_slice(2) do |topping, order|
      send("add_#{topping}", order.to_sym)
    end
  end

すると、以下のようなことができる。

jb = JiroBuilder.new
jb.abura_mashi_yasai_sukuname_karame_mashimashi
jiro = jb.jiro
p jiro.abura  # => #<Abura:0x007f8fe605d6d8 @volume=100>
p jiro.yasai  # => #<Yasai:0x007f8fe605d610 @volume=20>
p jiro.karame # => #<Karame:0x007f8fe605d570 @volume=300>

おおー。たのしい。