Rubyデザインパターン 11日目 : Builder

Rubyデザインパターン学習のために、自分なりに読書の結果をまとめていくことに決めました。第11日目はBuilderです。(http://www.amazon.co.jp/gp/product/4894712857/ref=as_li_qf_sp_asin_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=4894712857&linkCode=as2&tag=morizyun00-22)

![スクリーンショット 2015-07-27 11.25.28.png](https://qiita-image-store.s3.amazonaws.com/0/80075/d68a4010-c177-4ef5-2f2e-899a76c76cb6.png)


# **<i class="fa fa-pencil"></i>　11日目 Builder**
Builderは、オブジェクトを組み立てていくためのパターンです。
前回に学習したFactoryとBuilderは、オブジェクトを構築するという点で似通ったところが多いです。
大きな違いは、

* Factory：正しいオブジェクトを**選択・生成**する
* Builder：複雑なオブジェクトを**生成・構築**する

という点です。

また拡張することで、BuilderにFactoryのようなオブジェクトを選択する機能を持たせることができます。


# **<i class="fa fa-code"></i> 普通のコード**

まずは何も考えずに、一つずつPCのパーツを追加して構築するコードを書いてみましょう！

```rb

class Computer
  attr_accessor :display
  attr_accessor :motherboard
  attr_accessor :drives

  def initialize(display = :crt, motherboard = Motherboard.new, drives = [])
    @motherboard = motherboard
    @drives = drives
    @display = display
  end
end

class CPU
  # CPU共通のコード
end

class BasicCPU < CPU
  # あまり高速でないCPUに関するコード
end

class TurboCPU < CPU
  # 高速なCPUに関するコード
end

class Motherboard
  attr_accessor :cpu
  attr_accessor :memory_size

  def initialize(cpu = BasicCPU.new, memory_size = 1000)
    @cpu = cpu
    @memory_size = memory_size
  end
end

class Drive
  attr_accessor :type
  attr_accessor :size
  attr_accessor :writable

  def initialize(type, size, writable)
    @type = type
    @size = size
    @writable = writable
  end
end

motherboard = Motherboard.new(TurboCPU.new, 4000)

drives = []
drives << Drive.new(:hard_drive, 200000, true)
drives << Drive.new(:cd, 760, true)
drives << Drive.new(:dvd, 4700, false)

computer = Computer.new(:lcd, motherboard, drives)
```

以上のコードが、何も考えずにPCを組み立てるコードを書いた結果です。
それぞれの部品ごとのクラスはいいのですが、それを組み立てるインターフェイスが非常に煩雑で、可読性もよろしくないので、**一目で何をやっているかがわかりません！**

なにかいい手はないのでしょうか？

# **<i class="fa fa-code"></i>　Builderでオブジェクトを組み立てよう！**

```rb
class ComputerBuilder
  attr_reader :computer # builder.computerでアクセスしてcomputerオブジェクトを得るため

  def initialize
    @computer = Computer.new # Computerオブジェクトを作成
  end

  def turbo(has_turbo_cpu = true)
    @computer.motherboard.cpu = TurboCPU.new
  end

  def display=(display)
    @computer.display = display
  end

  def memory_size=(size_in_mb)
    @computer.motherboard.memory_size = size_in_mb
  end

  def add_cd(writer = false)
    @computer.drives << Drive.new(:cd, 760, writer)
  end

  def add_dvd(writer = false)
    @computer.drives << Drive.new(:dvd, 4000, writer)
  end

  def add_hard_disk(size_in_mb)
    @computer.drives << Drive.new(:hard_disk, size_in_mb, true)
  end
end

builder = ComputerBuilder.new
builder.turbo
builder.add_cd(true)
builder.add_dvd
builder.add_hard_disk(100000)

computer = builder.computer # 最終的に求めていたComputerオブジェクトが得られます
```

GoFはBuilderオブジェクトを使うクライアントをディレクタと呼んでいます。
ディレクタは新しいオブジェクトの組み立てをビルダ(ここではComputerBuilder)に指示します。
作られたオブジェクトはプロダクト(computer)と呼ばれます。

こういった形で、**邪魔なオブジェクト生成の部分を全てビルダに分離 <i class="fa fa-puzzle-piece"></i>**することができました。

冒頭で紹介したように、オブジェクトを組み立てるだけではなく、ビルダを抽象化することによってオブジェクトを選択する機能もつけることができます。

```rb
class ComputerBuilder
  attr_reader :computer

  def turbo(has_turbo_cpu = true)
    @computer.motherboard.cpu = TurboCPU.new
  end

  def memory_size=(size_in_mb)
    @computer.motherboard.memory_size = size_in_mb
  end
end

class DesktopBuilder < ComputerBuilder
  def initialize
    @computer = DesktopComputer.new
  end

  def display=(display)
    @display = display
  end

  def add_cd(writer = false)
    @computer.drives << Drive.new(:cd, 760, writer)
  end

  def add_dvd(writer = false)
    @computer.drives << Drive.new(:dvd, 40000, writer)
  end

  def add_hard_disk(size_in_mb)
    @computer.drives << Drive.new(:hard, size_in_mb, true)
  end
end

class LaptopBuilder < ComputerBuilder
  def initialize
    @computer = LaptopComputer.new
  end

  def display=(display)
    raise "Laptop display must be lcd" unless display == :lcd
  end

  def add_cd(writer = false)
    @computer.drives << LaptopDrive.new(:cd, 760, writer)
  end

  def add_dvd(writer = false)
    @computer.drives << LaptopDrive.new(:dvd, 4000, writer)
  end

  def add_hard_disk(size_in_mb)
    @computer.drives << LaptopDrive.new(:hard_disk, size_in_mb, true)
  end
end

laptop_builder = LaptopBuilder.new
laptop_builder.display = :lcd
# etc
# etc
# etc
laptop_builder.computer
```

## <i class="fa fa-lock"></i> オブジェクト生成を安全にする

```rb
def computer
  raise "Not enough memory" if @computer.motherboard.memory_size < 250
  raise "Too many drives" if @computer.drives.size > 4
  hard_disk = @computer.drives.find{ |drive| drive.type == :hard_disk }
  raise "No hard disk." unless hard_disk
  @computer
end
```

最後の`.computer`でコンピュータオブジェクトを作成する時点の話です。
ここで、基底クラスの`.computer`をオーバーライドしておくことにより、属性チェックをしてから、それを通過した規格を満たすものだけをオブジェクトとして世に送り出すことができます。

`attr_accessor`もメソッドなのでオーバーライドできることもワンポイントです <i class="fa fa-lightbulb-o"></i>


## <i class="fa fa-diamond"></i> Let's Ruby !!!

これまでのコードでBuilderパターンは完成したのですが、依然として組み立てのときにたくさんのメソッドを呼ぶ必要があります。
これを解決する方法は**マジックメソッド**です。

それはなんだというと、Builderのクライアントが特定のパターンに従ったメソッド名を組み立て、それをビルダに適用するというものです。

```rb
def method_missing(name, *args)
  words = name.to_s.split('_')
  return super(name, *args) unless words.shift == 'add'
  words.each do |word|
    next if word == 'and'
    add_cd if word == 'cd'
    add_dvd if word == 'dvd'
    add_hard_disk(100000) if word == 'harddisk'
    turbo if word == 'turbo'
  end
end
```

手順的には、予期せぬメソッドを必ず受け取るというのを前提にしておいて、`method_missing`で補足する、というパターンです。
これによって、

```rb
builder.display
builder.motherboard
builder.drive
builder.accessories
##### so many...
```
のように面倒な繰り返しをしなくてよくなりますね。


`rails generate migration add_post_id_to_users`
Railsコマンドでも内部はこういった形の実装になっているのかもしれませんね!(詳しくは知りません!)




# **<i class="fa fa-star"></i>　まとめ**

Builderパターンはオブジェクトをだんだんと組み立てていくパターンです。
組み立てたいオブジェクトのクラスが巨大になるほど、Builderが威力を発揮することは多いようです。
オブジェクトを組み立てていった際に、「めんどくさいな...」と思ったら導入するメリットがあるかもしれません。