「HeadFirstデザインパターン」と「Rubyによるデザインパターン」を読んで Builder パターン

何番煎じか判りませんがお勉強メモを残します

「[HeadFirstデザインパターン](https://www.amazon.co.jp/dp/4873112494)」第13章
「[Rubyによるデザインパターン](https://www.amazon.co.jp/dp/4894712857)」第14章

# Builder パターン

#### 「HeadFirstデザインパターン」でのJavaコードは（だいたい）こんな感じ

第13章にて「その他のパターン」として2ページで紹介されている

- 製品の構築をカプセル化し、段階的な製品構築を可能にする
- 複数の、手順の変化するプロセスにてオブジェクトを構成できる(一度の手順によるFatoryパターンとは対照的)
- 製品の内部表現をクライアントから隠蔽する
- クライアントには抽象インターフェースしか見えない為、製品の実装を容易に交換できる
- 複合構造の構築によく使われる
- オブジェクトの構築には、Factoryパターンを使うときよりもクライアントのドメインに対する知識が必要となる

Javaでの実装コードは掲載されていない(´・ω・｀)

#### 「Rubyによるデザインパターン」でのRubyコードは（だいたい）こんな感じ

- 前章ではファクトリによる正しいオブジェクトの取得方法を見てきた
- 正しいオブジェクトを取得する事が問題なのではなく、オブジェクトを構成する時が問題になる場合がある

<br>

- 我々はデスクトップPCを製造するビジネスを支援するためのシステムを作っているとする
- それぞれのPCはオーダーメイドで、我々は各PCに詰め込まれる部品を把握する必要がある
- 話をシンプルにする為に、PCはディスプレイとマザーボード、複数のドライブから構成されている、事とする

```ruby
class Computer
  attr_accessor :display, :motherboard
  attr_reader :drives

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

- ディスプレイは :crt か :lcd のどちらかしかないです
- マザーボードはある程度のメモリがあり、並のCPUか高速CPUかどちらかを持っています

```ruby
class CPU
  # CPU共通のコード
end

class BasicCPU < CPU
  # 並のCPUのコード
end

class TurboCPU < CPU
  # 高速CPUのコード
end

class Motherboard
  attr_accessor :cpu, :memory_size

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

ドライブは3種類あります :hard_disk, :cd, :dvd

```ruby
class Drive
  attr_reader :type     # :hard_drive, :cd, :dvd
  attr_reader :size     # MB
  attr_reader :writable # ドライブが書き込み可能かどうか

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

↓単純なモデルにしたにもかかわらず、Computerの新しいインスタンスを組み立てるのは退屈な作業になります

```ruby
# たくさんのメモリを積んだ高速なコンピュータを組み立てる
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)
```

### Builderパターン

- Builderパターンの考え方は、この種の構築ロジックをとあるクラスにカプセル化するという単純なもの
- ビルダクラスが担当するのは、複雑なオブジェクトのコンポーネントの組み立て
- ビルダは新しいオブジェクトに対してステップごとに構成を支持するインターフェースを持っている

```ruby
class ComputerBuilder
  attr_reader :computer

  def initialize
    @computer = Computer.new
  end

  def turbo
    @computer.motherboard.cpu = TurboCPU.new
  new

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

  def memory_size=(memory_in_mb)
    @computer.motherboard.memory_size = memory_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, 4700, writer)
  end

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

- ComputerBuilderクラスはComputerのインスタンスを作るための全ての詳細を分離します
- ビルダの新しいインスタンスを単純に作成し、コンピュータに必要なすべてのオプションを指定するプロセスを1つずつ実行する

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

computer = builder.computer
```

- Gofはビルダオブジェクトのクライアントをディレクタ(director)と呼んでいる　ビルダに指示するものである
- 作られるオブジェクトは製品(Product)と呼ばれる
- ビルダは複雑なオブジェクトを作る負荷を軽減するだけでなく、その実装の詳細を隠蔽するする役割も持っている
- ディレクタは新しいオブジェクトを作るための詳細を知る必要は無い
- ComputerBuilderクラスを使うことで、DVDやハードディスクを表すクラスがどれなのか知る必要は無いという訳である

<br>

- Builderパターンは正しいクラスを見つけることに関心をおいているのではなく、オブジェクトを構成することに焦点をおいている
- 邪魔なオブジェクト生成のコードを分離するのがビルダの主な目的
- ビルダをオブジェク生成にすれば、「クラスの選択」をする際にも便利に使うことが出来る

#### PC製造ビジネスの続き

- ラップトップPC (ノートPC) も扱うようになります
- つまり デスクトップPC と ラップトップPC、2種類の商品を扱うようになります

```ruby
class DesctopComputer < Computer
  # デスクトップPCに関するコード
end

class LaptopComputer < Computer
  def initialize(motherboard=Motherboard.new, drives=[])
    super(:lcd, motherboard, drives)
  end

  # デスクトップPCに関するコード
end
```

- ラップトップPCの部品はデスクトップPCのそれとは同じではない
- ビルダの基底クラスを作り、2つのサブクラスでそれらの違いを扱うようにリファクタリングすることができる
- この抽象的なビルダの基底クラスで、2種類のコンピュータの共通部分を扱う

```ruby
class ComputerBuilder
  attr_reader :computer

  def turbo
    @computer.motherboard.cpu = TurboCPU.new
  new

  def memory_size=(memory_in_mb)
    @computer.motherboard.memory_size = memory_in_mb
  end
end
```

```ruby
class DesctopBuilder < 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, 4700, writer)
  end

  def add_hard_drive(size_in_mb)
    @computer.drives << Drive.new(:hard_drive, size_in_mb, true)
  end
end

# ラップトップPCのドライブはデスクトップPCとは違うので LaptopDrive.new を使うようになっています

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

  def display=(display)
    raise "LCD only" if 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, 4700, writer)
  end

  def add_hard_drive(size_in_mb)
    @computer.drives << LaptopDrive.new(:hard_drive, size_in_mb, true)
  end
end
```

- ビルダはオブジェクトの生成を簡単にするだけでなく、より安全にすることができる
- ビルダの最後に実行する「できあがったオブジェクトを下さい」メソッドはクライアントから要求された構成が、ルールに則っているかチェックするための理想的な場所になる

```ruby
class ComputerBuilder
  def computer
    raise 'メモリなし'   if @computer.motherboard.memory_size < 250
    raise 'ドライブ多杉' if @computer.drives.size > 4
    raise 'HDなし'       if drives.find { |d| d.type == :hard_drive }.nil?

    @computer
  end
end
```

#### まとめ

- Builderパターンの必要性はアプリケーションが複雑になるにつれて大きくなる
- 例えば、最初のComputerクラスはCPUの型やメモリの大きさを把握できればよかっただけかもしれません。そのような単純なクラスへのビルダの適用は明らかにやりすぎ
- しかし、Computerクラスを拡張し、ドライブやオプションをモデル化し、それらのオプション間の依存関係が爆発するようならビルダはより意味を持つ
- Factoryパターンの時と同様に、Builderパターンを誤用してしまうよくあるケースは「必要ないのに使ってしまう」事
- ビルダを使うのは蔵祭する要求が手に負えなくなった時である