
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対応、読みづらい部分を修正