酒と泪とRubyとRailsと

Ruby on Rails と Objective-C は酒の肴です!

ビルダー Ruby 2.0.0 デザインパターン速攻習得[Builder][Design Pattern]

GoFのデザインパターン(Design Pattern)の一つ、ビルダー(Builder)をRubyのサンプルコードで紹介します。

ビルダーパターンは次のような場面で使われます。

オブジェクトの生成に大量のコードが必要
オブジェクトを作り出すのが難しい
オブジェクト生成時に必要なチェックを行いたい

ビルダーとは?

ビルダーは、作成過程を決定する「Director」 と作業インターフェースを持つ「Builder」を組み合わせることで、柔軟にオブジェクトを生成をすることができるデザインパターンです。

ビルダーには次の3つの構成要素があります。

ディレクタ(Director):Builderで提供されているインタフェースのみを使用して処理を行う
ビルダー(Builder):各メソッドのインタフェースを定める
具体ビルダー(ConcreteBuilder):Builderが定めたインタフェースの実装

サンプルソース1

今回のサンプルでは、砂糖水の作成について考えます。 まず、具体ビルダー(ConcreteBuilder)として、砂糖水クラスを作ります。 この砂糖水クラスでは、砂糖と水の量を変数として持ちます。

1
2
3
4
5
6
7
8
# SugarWater: 砂糖水クラス (ConcreteBuilder:ビルダーの実装部分)
class SugarWater
  attr_accessor :water, :sugar
  def initialize(water, sugar)
    @water = water
    @sugar = sugar
  end
end

次にBuilderとして、SugarWaterBuilderクラスを作ります。 このクラスは砂糖水を作るためのインターフェイスと考えることができます。 このクラスに以下の3つのメソッドを追加します。

add_sugar: 砂糖を加える
add_water: 水を加える
result: 砂糖水の状態を返す
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# SugarWaterBuilder: 砂糖水を生成するためのインターフェイス (Builder)
class SugarWaterBuilder
  def initialize
    @sugar_water = SugarWater.new(0,0)
  end

  # 砂糖を加える
  def add_sugar(sugar_amount)
    @sugar_water.sugar += sugar_amount
  end

  # 水を加える
  def add_water(water_amount)
    @sugar_water.water += water_amount
  end

  # 砂糖水の状態を返す
  def result
    @sugar_water
  end
end

最後に砂糖水を作るための作成過程(cook)のメソッドを持つ、Directorクラスを作ります。

1
2
3
4
5
6
7
8
9
10
11
12
13
# Director: 砂糖水の作成過程を取り決める
class Director
  def initialize(builder)
    @builder = builder
  end
  # 砂糖水の作成過程を定義する
  def cook
    @builder.add_water(150)
    @builder.add_sugar(90)
    @builder.add_water(300)
    @builder.add_sugar(35)
  end
end

このプログラムを呼び出します。これで砂糖水を生成することができます。

1
2
3
builder = SugarWaterBuilder.new
director = Director.new(builder)
director.cook

この実行結果の砂糖水は次のようになりました。

1
2
p builder.result
#=> <SugarWater:0x007fc773085bc8 @water=450, @sugar=125>

このようにBuilder側に作業を担当してもらい、Director側に作業過程を担当してもらうことで、オブジェクトの生成が柔軟にできるイメージを持てたと思います。

サンプルソース2

サンプル1では砂糖水を作成するだけでしたが、サンプル2では塩水も作成できるようにします。まず、塩水のSaltWaterクラスを追加します。

1
2
3
4
5
6
7
8
9
10
11
12
13
# SaltWater 塩水クラス (ConcreteBuilder:ビルダーの実装部分)
class SaltWater
  attr_accessor :water, :salt
  def initialize(water, salt)
    @water = water
    @salt = salt
  end

  # 素材(塩)を加える
  def add_material(salt_amount)
    @salt += salt_amount
  end
end

続いて砂糖水クラスを変更します。 素材を追加するクラスを塩水クラスと共通するために、メソッド名をadd_materialにします。

1
2
3
4
5
6
7
8
9
10
11
12
13
# SugarWater: 砂糖水クラス (ConcreteBuilder:ビルダーの実装部分)
class SugarWater
  attr_accessor :water, :sugar
  def initialize(water, sugar)
    @water = water
    @sugar = sugar
  end

  # 素材(砂糖)を加える
  def add_material(sugar_amount)
    @sugar += sugar_amount
  end
end

続いてBuilderの変更を行います。変更点は次の2点です。

  • Builderのクラス名をWaterWithMaterialBuilderクラスに変更
  • 素材を入れるメソッドの名称をadd_materialメソッドに変更
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# SugarWaterBuilder: 加工した水を生成するためのインターフェイス(Builder)
class WaterWithMaterialBuilder
  def initialize(class_name)
    @water_with_material = class_name.new(0,0)
  end

  # 素材を入れる
  def add_material(material_amount)
    @water_with_material.add_material(material_amount)
  end

  # 水を加える
  def add_water(water_amount)
    @water_with_material.water += water_amount
  end

  # 加工水の状態を返す
  def result
    @water_with_material
  end
end

最後にDirectorクラスです。こちらもadd_sugarメソッドをadd_materialメソッドに変更しています。

1
2
3
4
5
6
7
8
9
10
11
12
# Director: 加工水の作成過程を取り決める
class Director
  def initialize(builder)
    @builder = builder
  end
  def cook
    @builder.add_water(150)
    @builder.add_material(90)
    @builder.add_water(300)
    @builder.add_material(35)
  end
end

変更したソースでの結果を確認します。 まず砂糖水の生成です。サンプルソース1と同様の結果になりました。

1
2
3
4
5
6
builder = WaterWithMaterialBuilder.new(SugarWater)
director = Director.new(builder)
director.cook

p builder.result
#=> #<SugarWater:0x007fc773085bc8 @water=450, @sugar=125>

続いて、塩水の生成です。塩水が生成されていることがわかります。

1
2
3
4
5
6
builder = WaterWithMaterialBuilder.new(SaltWater)
director = Director.new(builder)
director.cook

p builder.result
# #<SaltWater:0x007f92cc103ba8 @water=450, @salt=125>

このサンプルソースはGitHubにも置いています。

サンプルソース(GitHub)

Special Thanks

デザインパターンbuilder

デザインパターン-Builder

Amazon.co.jp: Rubyによるデザインパターン: Russ Olsen, ラス・オルセン, 小林 健一, 菅野 裕, 吉野 雅人, 山岸 夢人, 小島 努: 本

変更来歴

12/10 09:00 GitHubへのサンプルソースの設置。導入文の修正
12/10 15:25 「どんな時に使うか?」、「サンプルソース2の説明」を追加
12/11 00:00 書籍へのリンクをAmazon アフィリエイトに変更
06/21 23:05 Ruby2.0.0対応、読みづらい部分を修正

おすすめの書籍