2010-07-19
Builderパターン
目的
複合オブジェクトの生成において、作成過程を抽象化することにより、同じ作成過程で異なるオブジェクトを生成できるようにする。
動機
Builderということで建築を例に考えてみます。
<?php class Room { private $_type; public function __construct($type) { $this->_type = $type; } } class House { private $_type; private $_rooms; private $_roof; public function __construct() { } public function setType($str) { $this->_type = $str; } public function addRoom(Room $room) { $this->_rooms[] = $room; } public function addRoof() { $this->_roof = '屋根'; } } class HouseDirector { private $builder; public function __construct(HouseBuilder $builder) { $this->builder = $builder; } public function construct() { $this->builder->buildFrame(); $this->builder->buildRoom('キッチン'); $this->builder->buildRoom('リビング'); $this->builder->buildRoom('ベッドルーム'); $this->builder->buildRoom('和室'); $this->builder->buildRoom('トイレ'); $this->builder->buildRoof(); } } class HouseBuilder { public function buildFrame() {} public function buildRoom($str) {} public function buildRoof() {} public function getHouse() {} } class WoodHouseBuilder extends HouseBuilder { private $_house; public function __construct() { $this->_house = new House(); } public function buildFrame() { $this->_house->setType('木造'); } public function buildRoom($roomType) { $this->_house->addRoom(new Room($roomType)); } public function buildRoof() { $this->_house->addRoof(); } public function getHouse() { return $this->_house; } } class SteelHouseBuilder extends HouseBuilder { private $_house; public function __construct() { $this->_house = new House(); } public function buildFrame() { $this->_house->setType('鉄骨'); } public function buildRoom($roomType) { $this->_house->addRoom(new Room($roomType)); } public function buildRoof() { $this->_house->addRoof(); } public function getHouse() { return $this->_house; } }
以下クライアントコード。
<?php $type = 'wood'; switch ($type) { case 'wood': $builder = new WoodHouseBuilder(); break; case 'steel': $builder = new SteelHouseBuilder(); break; default: exit; } $director = new HouseDirector($builder); $director->construct(); var_dump($builder->getHouse());
- 依頼人(クライアント)が家(Houseオブジェクト)を建てるとき、木造(WoodHouse)と鉄骨(SteelHouse)二つの種類から選べます。種類によって建築者も違います。
- 依頼人は、どの構造(建築者)で家を作りたいかを現場監督(HouseDirector)に伝え、あとは建築をお願いするだけです。
- その後、現場監督から建築者(HouseBuilder)にどういう過程で建築するか指示が渡ります。
- 建築者はその支持に従いながら、部屋(Roomオブジェクト)を作って、それを組み合わせて家を建てます。
- 他の依頼人が同じ現場監督に鉄骨で建てたいと言ったときも、建築者が変わるだけで、現場監督は同じ作成過程で指示を出します。
構造
Product
Builder
ConcreteBuilder
Director
利点
Productオブジェクトの内部構造の変更が容易
Productオブジェクトの作成過程、内部構造は、ConcreteBuilderのみに依存しているため、内部構造を変更したい場合は、専用のConcreteBuilderを定義するだけで済みます。
また、ConcreteBuilderクラスはBuilderクラスの実装なので、Directorが使うメソッドが保証されています。
そのため、クライアントから、生成するProductオブジェクトの切り替えが安全かつ容易にできるわけですね。
Productオブジェクトの作成過程や表現方法を局所化する
Productオブジェクトの作成過程や表現方法をカプセル化することにより、それぞれの独立性を高めています。
上記のProductオブジェクトの追加、切り替えが容易に行なえる理由の一つでもあります。
また、別のDirectorクラスからでも、既存のConcreteBuilderオブジェクトを委譲すれば、対象のProductオブジェクトを生成することができ、再利用性が高いです。
Productオブジェクトの作成過程を細かくコントロールできる
Builderパターンでは、DirectorクラスのコントロールのもとProductオブジェクトを段階的に作成するため、段階ごとに細かくプロセスを定義することができます。
実装上でのポイント
Builderクラスのメソッドをデフォルトで空にする
Builderクラスが宣言するメソッドは、抽象メソッドではなく空のメソッドを定義することもできます。
ConcreteBuilderクラスでは、使いたいメソッドだけオーバーライドして実装することによって、Builderクラスを汎用性の高いインターフェースとして提供することができます。
上記のHouseBuilderクラスは、空メソッドで定義しています。
問題提起
分かる方いましたら、教えて欲しいです・・・
Productオブジェクトは複合オブジェクトでなければいけないのか?
Builderパターンは、同じ作成過程で異なるオブジェクトを生成できることが特徴であるため、生成したProductオブジェクトは複合オブジェクトである必要はないのでは?
次の事項もあって、Builderパターンの本来の目的が正直分からなくなってきてしまいました。
ConcreteBuilderクラスは必ずしもProductオブジェクトを生成する必要はない?
GoF本のサンプルとして、下記のような特殊な例があります。
class CountingMazeBuilder:public MazeBuilder { public: CountingMazeBuilder(); virtual void BuildMaze(); virtual void BuildRoom(int); virtual void BuildDoor(int, int); virtual void AddWall(int, Direction); void GetCounts(int&, int&) const; private: int _doors; int _rooms; } CountingMazeBuilder::CountingMazeBuilder () { _rooms = _doors = 0; } void CountingMazeBuilder::BuildRoom (int) { _rooms++; } void CountingMazeBuilder::BuilderDoor (int, int) { _doors++; } void CountingMazeBuilder::GetCounts (int& rooms, int& doors) const { rooms = _rooms; doors = _doors; }
これはConcreteBuilderクラスに当たるのですが、複合オブジェクトどころかオブジェクト自体生成していません!
Builderパターンはオブジェクト生成のためのパターンであるにも関わらず、その目的すらも外れてしまいました。
単純に汎用性の高いパターンだと思っておけば良いのでしょうか?
Template methodパターンとの違いは?
上記のようにオブジェクトを生成しないのであれば、Template methodパターンと何が違うの?という話になります。
Builderパターンでは、テンプレートメソッドに当たる部分を一つのクラス(Director)として局所化しています。
なので、作成過程だけを変えたい場合はDirectorを増やすことで、既存のクラスに影響を与えることなく解決します。
Template methodパターンで作成過程のみ変えたいとなると、テンプレートメソッドを追加することになるので、そのたびにAbstractClassが肥大化していきます。
また、メソッド名も変える必要があるので、クライアント側はどのメソッドがどういう作成過程か知っておく必要があります。
そういう拡張がありなのかどうか分かりませんが・・・
Template methodパターンでテンプレートメソッドを増やす必要が出てきたら、Builderパターンに変えたほうが良いってことですね!

