Builderパターンとはどういうものか
まず最初に、builderは建築業者を意味する言葉です。
今回のパターンは同じ作成過程で異なった結果を得るためのものとなります。
考え方として、建物を建てる際に、完成までに必要な要素として「構築の過程」と「素材」があると考えます。
「過程」では、どの順で、どこに何を配置するか、「素材」では、柱や壁、屋根は何で作るか、という形です。
「過程」「素材」それぞれ様々な組み合わせが考えられます。
そこで、「過程」「素材」のそれぞれを個別で用意しておくことで、色々な用途に柔軟に対応できるようになります。
Builderパターンは、このような**「過程」を決定するDirectorと呼ばれるものと「素材(表現形式)」を決定するBuilder**と呼ばれるものを組み合わせることで、オブジェクトの生成をより柔軟にし、そのオブジェクトの「過程」をもコントロールすることができるようにするためのパターンです。
[参考サイト]https://www.techscore.com/tech/DesignPattern/Builder.html/
Builderパターンを用いるメリット/デメリット
-
メリット
-
複雑なオブジェクトの構築方法をカプセル化できる
-
複数の素材を用いることで多彩なオブジェクトを生成できる
-
デメリット
-
シンプルな構造のインスタンス生成に適応させた場合、意味もなく工数が増えてしまう
-
どのような情報を渡す必要があるのかクライアント側が認識している必要がある
※コンストラクタの引数が多い場合にも良いと記載のある部分もありそちらもメリットであるが、
今回のサンプルでは引数を外部から渡す形であるため外してある。
(builder内部に初期化メソッドを用意しdirectorで実行するのみの構造とすれば大きなメリットとなるが自由度はやや落ちる)
[参考サイト]
- http://doriven.hatenablog.com/entry/2014/11/08/040739
- https://debimate.jp/2020/04/25/%E3%80%90builder%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%80%91%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96new%E5%BC%95%E6%95%B0%E3%81%8C/
サンプルコード
仕様
料理のインスタンスを最終的に生成する。材料を引数とし、builderではその調理プロセスを表す形とした。
以下を用意します。
-
CuisineBuilderInterface(インターフェース)
-
各調理プロセスのbuilderの動作保証をします。
-
CuisineInfoクラス
-
返すインスタンスの形式を指定します。
-
GrilledFoodBuilderクラス
-
渡された素材を「焼く」役割
-
StewedFoodBuilderクラス
-
渡された素材を「煮る」役割
-
CuisineDirectorクラス
-
渡されたbuilder(過程)を用いて、指定された材料で作成を行う
では実際に書いてみる
class CuisineInfo
{
private $material1;
private $material2;
private $cuisineString;
public function __construct($material1, $material2, $cuisineString)
{
$this->material1 = $material1;
$this->material2 = $material2;
$this->cuisineString = $cuisineString;
}
// 第一材料を返します。
public function getMaterial1()
{
return $this->material1;
}
// 第二材料を返します。
public function getMaterial2()
{
return $this->material2;
}
// 結果文字列
public function getcuisineString()
{
return $this->material1 . 'と' . $this->material2 . $this->cuisineString;
}
}
先ず返されるインスタンスの形式をこちらで指定
interface CuisineBuilderInterface
{
public function getResult($material1, $material2);
public function getCookingMethodString();
}
上記インターフェースでインスタンス作成の動作保証を行う。
以下builder
class GrilledFoodBuilder implements CuisineBuilderInterface
{
const COOKING_METHOD_STRING = 'を焼いたものです。';
public function getResult($material1, $material2)
{
// 量および濃度が無い場合はnull
if (!$material1 || !$material2) {
return null;
}
return new CuisineInfo($material1, $material2, $this->getCookingMethodString());
}
public function getCookingMethodString()
{
return self::COOKING_METHOD_STRING;
}
}
class StewedFoodBuilder implements CuisineBuilderInterface
{
const COOKING_METHOD_STRING = 'を煮込んだものです。';
public function getResult($material1, $material2)
{
// 量および濃度が無い場合はnull
if (!$material1 || !$material2) {
return null;
}
return new CuisineInfo($material1, $material2, $this->getCookingMethodString());
}
public function getCookingMethodString()
{
return self::COOKING_METHOD_STRING;
}
}
内容がほぼ同じとなってしまいましたが文字列違うので大目に見ていただければ、、
以下director
class CuisineDirector
{
private $builder;
private $material1;
private $material2;
public function __construct($builder, $material1, $material2)
{
$this->builder = $builder;
$this->material1 = $material1;
$this->material2 = $material2;
}
public function getCuisine()
{
return $this->builder->getResult($this->material1, $this->material2);
}
}
Directorクラスで指定されたbuilder(過程)、指定された素材でのオブジェクトの生成を指示し、結果物を返す。
呼び出してみる
$builder = new GrilledFoodBuilder();
$material1 = '牛肉';
$material2 = 'もやし';
$director = new CuisineDirector($builder, $material1, $material2);
$cuisineData = $director->getCuisine();
echo $cuisineData->getMaterial1() . PHP_EOL;
echo $cuisineData->getMaterial2() . PHP_EOL;
echo $cuisineData->getcuisineString() . PHP_EOL;
$builder = new StewedFoodBuilder();
$material1 = '豚肉';
$material2 = 'ジャガイモ';
$director = new CuisineDirector($builder, $material1, $material2);
$cuisineData = $director->getCuisine();
echo $cuisineData->getMaterial1() . PHP_EOL;
echo $cuisineData->getMaterial2() . PHP_EOL;
echo $cuisineData->getcuisineString() . PHP_EOL;
/*
牛肉
もやし
牛肉ともやしを焼いたものです。
豚肉
ジャガイモ
豚肉とジャガイモを煮込んだものです。
*/
上記のように期待した結果が得られた。
この規模であればbuilderクラス自体をインスタンス化して返しても良いかなとも思います。というかその方が良いかも、、
(CuisineInfoの処理をbuilderに持たせる)
まとめ
- Builder(過程)、素材の組み合わせによって多彩な形式のオブジェクトを生成できるという柔軟さがある
- 「過程」が共通のものがあり、なおかつ「素材」もいくつか共通な部分があるような状況で大きな効果を発揮しそうであると感じた。