ビルダ (Builder) | Ruby デザインパターン


GoFのデザインパターンのひとつ、ビルダ(Builder)をRubyコードで紹介します。
ビルダパターンは次のような場面で使われます。

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

🚌 ビルダとは?

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

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

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

🍄 サンプルソース1

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

# 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: 砂糖水の状態を返す
# 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クラスを作ります。

# 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

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

builder = SugarWaterBuilder.new
director = Director.new(builder)
director.cook

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

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

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

🎂 サンプルソース2

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

# 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にします。

# 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メソッドに変更
# 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メソッドに変更しています。

# 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と同様の結果になりました。

builder = WaterWithMaterialBuilder.new(SugarWater)
director = Director.new(builder)
director.cook
p builder.result
#=> #<SugarWater:0x007fc773085bc8 @water=450, @sugar=125>

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

builder = WaterWithMaterialBuilder.new(SaltWater)
director = Director.new(builder)
director.cook
p builder.result
# #<SaltWater:0x007f92cc103ba8 @water=450, @salt=125>

🐹 GitHubリポジトリ

🐞 参考リンク

📚 おすすめの書籍