Builder
一言でいうと
Builder (ビルダー、 建設業者) は、 複雑なオブジェクトを段階的に構築できる生成に関するデザインパターンです。 このパターンを使用すると、 同じ構築コードを使用して異なる型と表現のオブジェクトを生成することが可能です。
問題
多くのフィールドと入れ子になったオブジェクトからなり、 面倒な段階的初期化が必要な、 複雑なオブジェクトを想像してみてください。 このような初期化コードは通常、 多くのパラメータを持つ巨大なコンストラクターの中に埋め込まれています。 最悪の場合、 初期化コードはクライアント・コードの中のあちこちに分散しています。
たとえば、 House
(家) オブジェクトをどう作成するか考えてみましょう。 簡単な家を建てるには、 壁を 4 面と床を一つ作り、 ドアを一つ設置し、 窓を二つ取り付け、 屋根を作ればすみそうです。 しかし、 もっと大きくて明るい、 裏庭やその他の設備 (暖房システム、 配管、 電気配線のような) のある家を望んでいるとしたら、 どうしますか?
最も単純な解決策は、 基底の House
クラスを拡張し、 あらゆる組み合わせをカバーする多数のサブクラスを作成することです。 こうすると最終的にはとんでもない数のサブクラスが必要になります。 ベランダのスタイルのような新しいパラメーターを追加すると、 この階層はさらに複雑になります。
もう一つのやり方では、 サブクラスの繁殖は不要です。 基底の House
クラス中に、 家オブジェクトに関わるありとあらゆるパラメーターを持った巨大コンストラクターを作ります。 このやり方では、 サブクラスの必要性を排除しますが、 別の問題が発生します。
ほとんどの場合、 大半のパラメータは使われることなく、 かなり醜いコンストラクター呼び出しになります。 たとえば、 プールがある家はわずかしかないため、 プールに関連するパラメーターは、 9 割の場合、 役立たずとなります。
解決策
Builder パターンでは、 オブジェクトの構築コードを自クラスから抽出して、 ビルダーと呼ばれる別オブジェクトに移動します。
このパターンでは、 オブジェクトの構築を buildWalls
(壁作成)、 buildDoor
(ドア作成) などの、 一連のステップに整理します。 オブジェクトを作成するには、 ビルダー・オブジェクトに対してこれらのステップを次々に実行します。 重要なのは、 すべてのステップを呼び出す必要はない、 ということです。 特定の構成のオブジェクトを生成するためには、 それに必要なステップのみを呼び出せばすみます。
様々な種類のプロダクトを構築する必要がある場合、 構築ステップのいくつかは、 異なる実装を必要とするかもしれません。 たとえば、 小屋の壁は木でできていますが、 城壁は石である必要があります。
この場合、 同じ一連の構築ステップを異なる方法で実装した複数のビルダー・クラスを作成することができます。 次に、 異なる種類のオブジェクトを作り出すためには、 これらのビルダーを構築プロセス (一連の決まった順番でのステップの呼び出し) で使用します。
たとえば、 木やガラスからすべてを作るビルダーを想像してみてください。 二つ目のビルダーは、 石と鉄を使い、 三つ目は、 金とダイヤモンドを使います。 同じステップを呼び出すと、 最初のビルダーから通常の家が手に入ります。 2 番目のビルダーからは、 小さな城、 3 番目のビルダーからは、 宮殿が手に入ります。 しかし、 このことを可能にするためには、 構築ステップを呼び出すクライアント・コードが共通のインターフェースを使用してビルダーとやりとりする必要があります。
ディレクター
この考えをさらに進めて、 プロダクト構築に使用する一連の構築ステップへの呼び出しをディレクターと呼ばれる別クラスに抽出することができます。 ディレクター・クラスは構築ステップを実行する順番を定義し、 ビルダーはそのステップの実装を提供します。
ディレクター・クラスは厳密には必要ありません。 クライアント・コードから直接、 特定の順序で構築ステップを呼び出せばすみます。 しかし、 ディレクター・クラスは、 再利用のために、 様々なよく使う構築ステップの一連の呼び出しを置いておくには、 良い場所かもしれません。
また、 ディレクター・クラスを使用すると、 プロダクト構築の詳細をクライアント・コードから完全に隠蔽できます。 クライアントはビルダーをディレクターに関連付け、 ディレクターを通して構築を開始し、 ビルダーから結果を取得するだけです。
構造
-
ビルダー (Builder) インターフェースは、 すべての型のビルダーに共通するプロダクト構築の様々なステップを宣言します。
-
具象ビルダー (Concrete Builder) は、 様々な構築ステップの実装を行います。 具象ビルダーは、 共通インターフェース外のプロダクトの産出も可能です。
-
プロダクト (Product) は、 結果のオブジェクトです。 異なるビルダーによって構築された製品は、 同じクラス階層やインターフェースに属する必要はありません。
-
ディレクター (Director) クラスは、 構築ステップを呼び出す順序を定義し、 プロダクトの特定の設定を作成して再利用できます。
-
クライアント (Client) は、 ビルダー・オブジェクトの一つをディレクターに関連付ける必要があります。 通常、 ディレクターのコンストラクターのパラメータを介して 1 回だけ実行されます。 その後、 ディレクターはそのビルダー・オブジェクトをその後のすべての構築に使用します。 しかし、 クライアントがビルダー・オブジェクトをディレクターの産出メソッドに渡すタイミングに関しては、 別のやり方もあります。 この場合、 ディレクターが何かの産出をするごとに、 別のビルダーを使用できます。
擬似コード
この Builder パターンの適用例では、 異なる型のプロダクトの構築に際して同じオブジェクト構築コードを再利用する方法を説明します。 車の構築とその車のマニュアルの作成を行います。
車は 100 以上の方法で作れる複雑な物体です。 巨大なコンストラクターで Car
クラスを複雑怪奇なものにする代わりに、 車生産のコードを、 独立した車のビルダー・クラスに抽出しました。 このクラスには、 車の様々なパーツを構成するための一連のメソッドがあります。
クライアントのコードが特注モデルの車を組み立てる必要がある場合は、 ビルダーを直接使うことができます。 一方、 クライアントは、 組み立てをディレクター・クラスに委任することもできます。 ディレクターは、 最も人気のあるモデルの車をビルダーを使って作る方法を知っています。
ショックを受けるかもしれませんが、 すべての車にはマニュアルが必要です。 (本当に読む人いるのか?) マニュアルは、 車のすべての機能を説明するため、 マニュアルの詳細はモデルごとに異なります。 だからこそ、 既存の構築手順を本物の車とそのマニュアルの作成の両方に再利用することは理にかなっています。 勿論マニュアルを作るのは車を作るのと同じではありません。 マニュアルの作成に特化したもう一つのビルダー・クラスが必要となります。 このクラスは車を作るクラスと兄弟関係にあり、 同じ構築メソッドの組を実装しますが、 車の部品を作る代わりにそれらを記述します。 これらのビルダーを同じディレクトリー・オブジェクトに渡すことで、 車かマニュアルのどちらかを作ることができます。
最後の部分は、 結果のオブジェクトの取り出しです。 鉄でできた車と紙のマニュアルは、 関連してはいますが、 とても異なるものです。 ディレクターに結果を取り出すメソッドを追加するためには、 ディレクターをプロダクトの具象クラスに結合する必要が出てきます。 そこで (結合を不要とするため)、 実際の作業を行ったビルダーから、 構築結果を受け取ります。
適応性
「望遠鏡的な」 コンストラクターを避けるために、 Builder パターンを使用します。
たとえば、 10 個の省略可能パラメータを持つコンストラクターがあるとします。 このような難物を呼び出すことは非常に不便です。 したがって、 コンストラクタ-を多重定義し、 少数パラメータの短縮版を作成します。 これらのコンストラクターは、 省略されたパラメーターの代わりにデフォルト値を元々のコンストラクターに渡します。
Builder パターンで、 必要なステップのみを使用して、 オブジェクトを段階的に作成できます。 このパターンを適用すれば、 コンストラクターに何十個ものパラメーターを詰め込む必要がなくなります。
ご自分のコードで異なる表現のプロダクト (石や木製の家など) を作成したい場合に、 Builder パターンを使ってください。
Builder パターンは、 製品の様々な表現の構築において、 詳細のみが異なる同様のステップが含まれる場合に適用できます。
基底のビルダー・インターフェースは、 すべての可能な構築ステップを定義し、 具象ビルダーは、 プロダクトの特定の表現を構築するために、 これらのステップを実装します。 一方、 ディレクター・クラスは、 構築の順序を手引きします。
Composite ツリーなどの複雑なオブジェクトの構築に、 Builder を適用してください。
Builder パターンでは、 プロダクトを段階的に構築します。 いくつかのステップの実行を遅らせても、 最終的プロダクトは、 問題なく機能します。 ステップを再帰的に呼び出すことも可能で、 オブジェクト・ツリーの構築時に便利です。
ビルダーは、 構築ステップの実行中の未完成のプロダクトを外部に公開しません。 これにより、 クライアントのコードが不完全な結果を取得することを防ぎます。
実装方法
-
利用できるすべてのプロダクトの表現 (種類) を構築するために必要な共通の構築ステップが、 明確に定義可能であることを確認してください。 そうでなければ、 パターンの実装に進むことはできません。
-
これらのステップを、 共通のビルダー・インターフェースで宣言します。
-
プロダクトの表現ごとに具象ビルダー・クラスを作成し、 その構築ステップを実装します。
構築の結果を取得するためのメソッドの実装をお忘れなく。 このメソッドがビルダーのインターフェース内で宣言できない理由は、 ビルダーによっては共通のインターフェースを持たないプロダクトを構築する可能性があるためです。 したがって、 メソッドの戻り値の型がわかりません。 ただし、 単一の階層にあるプロダクトを扱う場合は、 結果取得用メソッドを安全に共通インターフェースに追加することが可能です。
-
ディレクター・クラスの作成を検討してみてください。 同じビルダーのオブジェクトを使用して、 プロダクトを構築するための様々な方法を一箇所にまとめることが可能かもしれません。
-
クライアント・コードで、 ビルダーとディレクターの両方のオブジェクトを作成します。 構築開始前に、 クライアントはビルダーのオブジェクトをディレクターに渡す必要があります。 通常、 クライアントはディレクターのコンストラクターのパラメータを介して一度だけこれを行います。 ディレクターは、 そのビルダー・オブジェクトをその後のすべての構築で使用します。 それに代わるやり方として、 ディレクターの特定プロダクト用構築メソッドにビルダーを直接渡すということもできます。
-
すべてのプロダクトが同じインターフェースに従っている場合は、 構築結果をディレクターから直接取得できます。 そうでない場合は、 クライアントはビルダーから結果を取得する必要します。
長所と短所
- 段階的にオブジェクトを作成したり、 構築ステップを遅延させたり、 再帰的にステップを実行することが可能。
- プロダクトの様々な表現の作成に際して、 同じ構築コードの再利用が可能。
- 単一責任の原則。 複雑な構築用コードを、 プロダクトのビジネス・ロジックから分離可能。
- 本パターンでは、 複数の新規クラス作成の必要があるため、 コードの全体的な複雑さが増加。
他のパターンとの関係
-
多くの設計は、 まず比較的単純でサブクラスによりカスタマイズ可能な、 Factory Method から始まり、 次第に、 もっと柔軟だが複雑な Abstract Factory や Prototype や Builder へと発展していきます。
-
Builder は、 複雑なオブジェクトを段階的に構築することに重点を置いています。 Abstract Factory は、 関連するオブジェクトの集団を作成することに特化しています。 Abstract Factory がすぐにプロダクトを返すのに対して、 Builder ではプロダクトの取得前に、 いくつかの追加の構築のステップを踏まなければなりません。
-
Builder は、 複雑な Composite ツリー作成に使用できます。 構築ステップを再帰的に行なうように プログラムします。
-
Builder と Bridge を組み合わせることができます: ディレクター・クラスは抽象化層の役割を果たし、 ビルダーは実装です。
-
Abstract Factories、 Builders、 Prototypes はどれも Singletons で実装可能です。