春のセール

Builder

別名:ビルダー

一言でいうと

Builder ビルダー 建設業者 複雑なオブジェクトを段階的に構築できる生成に関するデザインパターンです このパターンを使用すると 同じ構築コードを使用して異なる型と表現のオブジェクトを生成することが可能です

Builder デザインパターン

問題

多くのフィールドと入れ子になったオブジェクトからなり 面倒な段階的初期化が必要な 複雑なオブジェクトを想像してみてください このような初期化コードは通常 多くのパラメータを持つ巨大なコンストラクターの中に埋め込まれています 最悪の場合 初期化コードはクライアント・コードの中のあちこちに分散しています

多くのサブクラスの存在が別の問題を引き起こします

オブジェクトの可能なすべての構成に対してサブクラスを作成すると プログラムが過剰に複雑になる可能性がある

たとえば House オブジェクトをどう作成するか考えてみましょう 簡単な家を建てるには 壁を 4 面と床を一つ作り ドアを一つ設置し 窓を二つ取り付け 屋根を作ればすみそうです しかし もっと大きくて明るい 裏庭やその他の設備 暖房システム 配管 電気配線のような のある家を望んでいるとしたら どうしますか

最も単純な解決策は 基底の House クラスを拡張し あらゆる組み合わせをカバーする多数のサブクラスを作成することです こうすると最終的にはとんでもない数のサブクラスが必要になります ベランダのスタイルのような新しいパラメーターを追加すると この階層はさらに複雑になります

もう一つのやり方では サブクラスの繁殖は不要です 基底の House クラス中に 家オブジェクトに関わるありとあらゆるパラメーターを持った巨大コンストラクターを作ります このやり方では サブクラスの必要性を排除しますが 別の問題が発生します

望遠鏡的コンストラクター

多数のパラメーターを持つコンストラクターには 常にすべてのパラメーターは必要ではない という欠点がある

ほとんどの場合 大半のパラメータは使われることなく かなり醜いコンストラクター呼び出しになります たとえば プールがある家はわずかしかないため プールに関連するパラメーターは 9 割の場合 役立たずとなります

解決策

Builder パターンでは オブジェクトの構築コードを自クラスから抽出して と呼ばれる別オブジェクトに移動します

Builder パターンの適用

Builder パターンでは 複雑なオブジェクトを一ステップずつ構築する Builder では プロダクト 構築対象物 の構築中に他のオブジェクトがプロダクトをアクセスすることは禁止されている

このパターンでは オブジェクトの構築を build­Walls 壁作成 build­Door ドア作成 などの 一連のステップに整理します オブジェクトを作成するには ビルダー・オブジェクトに対してこれらのステップを次々に実行します 重要なのは すべてのステップを呼び出す必要はない ということです 特定の構成のオブジェクトを生成するためには それに必要なステップのみを呼び出せばすみます

様々な種類のプロダクトを構築する必要がある場合 構築ステップのいくつかは 異なる実装を必要とするかもしれません たとえば 小屋の壁は木でできていますが 城壁は石である必要があります

この場合 同じ一連の構築ステップを異なる方法で実装した複数のビルダー・クラスを作成することができます 次に 異なる種類のオブジェクトを作り出すためには これらのビルダーを構築プロセス 一連の決まった順番でのステップの呼び出し で使用します

ビルダーは 同じタスクを異なる方法で実行する

たとえば 木やガラスからすべてを作るビルダーを想像してみてください 二つ目のビルダーは 石と鉄を使い 三つ目は 金とダイヤモンドを使います 同じステップを呼び出すと 最初のビルダーから通常の家が手に入ります 2 番目のビルダーからは 小さな城 3 番目のビルダーからは 宮殿が手に入ります しかし このことを可能にするためには 構築ステップを呼び出すクライアント・コードが共通のインターフェースを使用してビルダーとやりとりする必要があります

ディレクター

この考えをさらに進めて プロダクト構築に使用する一連の構築ステップへの呼び出しをと呼ばれる別クラスに抽出することができます ディレクター・クラスは構築ステップを実行する順番を定義し ビルダーはそのステップの実装を提供します

ディレクターは 機能するプロダクトを取得するために どの構築ステップを実行するべきかを知っている

ディレクター・クラスは厳密には必要ありません クライアント・コードから直接 特定の順序で構築ステップを呼び出せばすみます しかし ディレクター・クラスは 再利用のために 様々なよく使う構築ステップの一連の呼び出しを置いておくには 良い場所かもしれません

また ディレクター・クラスを使用すると プロダクト構築の詳細をクライアント・コードから完全に隠蔽できます クライアントはビルダーをディレクターに関連付け ディレクターを通して構築を開始し ビルダーから結果を取得するだけです

構造

Builder デザインパターンの構造Builder デザインパターンの構造
  1. ビルダー Builder インターフェースは すべての型のビルダーに共通するプロダクト構築の様々なステップを宣言します

  2. 具象ビルダー Concrete Builder 様々な構築ステップの実装を行います 具象ビルダーは 共通インターフェース外のプロダクトの産出も可能です

  3. プロダクト Product 結果のオブジェクトです 異なるビルダーによって構築された製品は 同じクラス階層やインターフェースに属する必要はありません

  4. ディレクター Director クラスは 構築ステップを呼び出す順序を定義し プロダクトの特定の設定を作成して再利用できます

  5. クライアント Client ビルダー・オブジェクトの一つをディレクターに関連付ける必要があります 通常 ディレクターのコンストラクターのパラメータを介して 1 回だけ実行されます その後 ディレクターはそのビルダー・オブジェクトをその後のすべての構築に使用します しかし クライアントがビルダー・オブジェクトをディレクターの産出メソッドに渡すタイミングに関しては 別のやり方もあります この場合 ディレクターが何かの産出をするごとに 別のビルダーを使用できます

擬似コード

この Builder パターンの適用例では 異なる型のプロダクトの構築に際して同じオブジェクト構築コードを再利用する方法を説明します 車の構築とその車のマニュアルの作成を行います

Builder パターン例の構造

車のステップごとの構築と その車のモデルのユーザーガイドの構築の例

車は 100 以上の方法で作れる複雑な物体です 巨大なコンストラクターで Car クラスを複雑怪奇なものにする代わりに 車生産のコードを 独立した車のビルダー・クラスに抽出しました このクラスには 車の様々なパーツを構成するための一連のメソッドがあります

クライアントのコードが特注モデルの車を組み立てる必要がある場合は ビルダーを直接使うことができます 一方 クライアントは 組み立てをディレクター・クラスに委任することもできます ディレクターは 最も人気のあるモデルの車をビルダーを使って作る方法を知っています

ショックを受けるかもしれませんが すべての車にはマニュアルが必要です 本当に読む人いるのか? マニュアルは 車のすべての機能を説明するため マニュアルの詳細はモデルごとに異なります だからこそ 既存の構築手順を本物の車とそのマニュアルの作成の両方に再利用することは理にかなっています 勿論マニュアルを作るのは車を作るのと同じではありません マニュアルの作成に特化したもう一つのビルダー・クラスが必要となります このクラスは車を作るクラスと兄弟関係にあり 同じ構築メソッドの組を実装しますが 車の部品を作る代わりにそれらを記述します これらのビルダーを同じディレクトリー・オブジェクトに渡すことで 車かマニュアルのどちらかを作ることができます

最後の部分は 結果のオブジェクトの取り出しです 鉄でできた車と紙のマニュアルは 関連してはいますが とても異なるものです ディレクターに結果を取り出すメソッドを追加するためには ディレクターをプロダクトの具象クラスに結合する必要が出てきます そこで 結合を不要とするため 実際の作業を行ったビルダーから 構築結果を受け取ります

// Builder パターンの使用は、製品が非常に複雑で細かな構成が必要な場合にの
// み意味がある。以下の二つのプロダクトは、共通のインターフェースは持たな
// いが、関連している。
class Car is
    // 車には、GPS とトリップコンピューターといくつかの座席がある。異なる
    // モデルの車(スポーツカー、SUV、オープンカー)には、異なる機能が搭
    // 載されている。

class Manual is
    // 車ごとに、その車の構成に従ったマニュアルがあり、全機能を説明する。


// ビルダー・インターフェースは、プロダクト・オブジェクトの異なる部品を作
// 成するメソッドを指定。
interface Builder is
    method reset()
    method setSeats(……)
    method setEngine(……)
    method setTripComputer(……)
    method setGPS(……)

// 具象ビルダー・クラスはビルダー・インターフェースに従い、構築ステップの
// 特定の実装を行う。プログラムには、いくつかの異種のビルダーがあるかもし
// れない。
class CarBuilder implements Builder is
    private field car:Car

    // できたてのビルダーのインスタンスは、今後の組み立てに使用される、空
    // のプロダクト・オブジェクトがある。
    constructor CarBuilder() is
        this.reset()

    // リセット・メソッドは、構築されるオブジェクトを一掃する。
    method reset() is
        this.car = new Car()

    // 全生産ステップは、同一のプロダクト・インスタンスに対して機能。
    method setSeats(……) is
        // 座席数を指定。

    method setEngine(……) is
        // 指定のエンジンを搭載。

    method setTripComputer(……) is
        // トリップコンピュータを取り付け。

    method setGPS(……) is
        // GPS を取り付け。

    // 具象ビルダーは、結果を取得するための独自のメソッドを提供することに
    // なっている。ビルダーによっては、まったく異なるプロダクトを生成する
    // 可能性があり、同じインターフェースに従えないため。したがって、結果
    // 取得のためのメソッドは、ビルダー・インターフェース(少なくとも静的
    // 型付けプログラミング言語)では宣言できない。
    //
    // 通常、最終結果をクライアントに返した後、ビルダーのインスタンスは別
    // のプロダクトの構築を開始する準備ができているものと期待される。その
    // ため、普通はプロダクト取得メソッドの中でリセット・メソッドを呼び出
    // す。 ただし、これは必須ではない。クライアント・コードからの明示的
    // なリセット呼び出しを待ってから今までの結果を廃棄してもよい。
    method getProduct():Car is
        product = this.car
        this.reset()
        return product

// 他の生成に関するパターンと異なり、Builder では、共通インターフェースに
// 従わないプロダクトを構築可能。
class CarManualBuilder implements Builder is
    private field manual:Manual

    constructor CarManualBuilder() is
        this.reset()

    method reset() is
        this.manual = new Manual()

    method setSeats(……) is
        // 車の座席の特徴に関する記述。

    method setEngine(……) is
        // エンジンに関する説明を追加。

    method setTripComputer(……) is
        // トリップコンピューターに関する説明を追加。

    method setGPS(……) is
        // GPS に関する説明を追加。

    method getProduct():Manual is
        // マニュアルを返し、ビルダーをリセット。


// ディレクターは、構築ステップを特定の順番で実行することのみの責任を持つ。
// プロダクトを特定の順番や構成で作り出す時に便利。クライアントはビル
// ダーを直接管理できるので、ディレクター・クラスは厳密には必須でない。
class Director is
    // ディレクターは、クライアント・コードが渡すどんなビルダー・インスタ
    // ンスとでも機能する。このため、クライアント・コードは、新規に組み立
    // てられたプロダクトの最終的な型を変更可能。ディレクターは、同一の
    // 構築ステップを使っていくつものプロダクトの異種を構築可能。
    method constructSportsCar(builder: Builder) is
        builder.reset()
        builder.setSeats(2)
        builder.setEngine(new SportEngine())
        builder.setTripComputer(true)
        builder.setGPS(true)

    method constructSUV(builder: Builder) is
        // ……


// クライアント・コードはビルダー・オブジェクトを作成し、それをディレク
// ターに渡し、構築プロセスを開始。最終結果はビルダーオブジェクトから取得。
class Application is

    method makeCar() is
        director = new Director()

        CarBuilder builder = new CarBuilder()
        director.constructSportsCar(builder)
        Car car = builder.getProduct()

        CarManualBuilder builder = new CarManualBuilder()
        director.constructSportsCar(builder)

        // ディレクターは具象ビルダーのクラスとプロダクトを認識していない
        // ため、最終製品はビルダー・オブジェクトから取得することが多い。
        Manual manual = builder.getProduct()

適応性

望遠鏡的な コンストラクターを避けるために Builder パターンを使用します

たとえば 10 個の省略可能パラメータを持つコンストラクターがあるとします このような難物を呼び出すことは非常に不便です したがって コンストラクタ-を多重定義し 少数パラメータの短縮版を作成します これらのコンストラクターは 省略されたパラメーターの代わりにデフォルト値を元々のコンストラクターに渡します

class Pizza {
    Pizza(int size) { …… }
    Pizza(int size, boolean cheese) { …… }
    Pizza(int size, boolean cheese, boolean pepperoni) { …… }
    // ……

このような化け物を作成することは C# や Java のようなメソッドの多重定義をサポートする言語でのみ可能です

Builder パターンで 必要なステップのみを使用して オブジェクトを段階的に作成できます このパターンを適用すれば コンストラクターに何十個ものパラメーターを詰め込む必要がなくなります

ご自分のコードで異なる表現のプロダクト 石や木製の家など を作成したい場合に Builder パターンを使ってください

Builder パターンは 製品の様々な表現の構築において 詳細のみが異なる同様のステップが含まれる場合に適用できます

基底のビルダー・インターフェースは すべての可能な構築ステップを定義し 具象ビルダーは プロダクトの特定の表現を構築するために これらのステップを実装します 一方 ディレクター・クラスは 構築の順序を手引きします

Composite ツリーなどの複雑なオブジェクトの構築に Builder を適用してください

Builder パターンでは プロダクトを段階的に構築します いくつかのステップの実行を遅らせても 最終的プロダクトは 問題なく機能します ステップを再帰的に呼び出すことも可能で オブジェクト・ツリーの構築時に便利です

ビルダーは 構築ステップの実行中の未完成のプロダクトを外部に公開しません これにより クライアントのコードが不完全な結果を取得することを防ぎます

実装方法

  1. 利用できるすべてのプロダクトの表現 種類 を構築するために必要な共通の構築ステップが 明確に定義可能であることを確認してください そうでなければ パターンの実装に進むことはできません

  2. これらのステップを 共通のビルダー・インターフェースで宣言します

  3. プロダクトの表現ごとに具象ビルダー・クラスを作成し その構築ステップを実装します

    構築の結果を取得するためのメソッドの実装をお忘れなく このメソッドがビルダーのインターフェース内で宣言できない理由は ビルダーによっては共通のインターフェースを持たないプロダクトを構築する可能性があるためです したがって メソッドの戻り値の型がわかりません ただし 単一の階層にあるプロダクトを扱う場合は 結果取得用メソッドを安全に共通インターフェースに追加することが可能です

  4. ディレクター・クラスの作成を検討してみてください 同じビルダーのオブジェクトを使用して プロダクトを構築するための様々な方法を一箇所にまとめることが可能かもしれません

  5. クライアント・コードで ビルダーとディレクターの両方のオブジェクトを作成します 構築開始前に クライアントはビルダーのオブジェクトをディレクターに渡す必要があります 通常 クライアントはディレクターのコンストラクターのパラメータを介して一度だけこれを行います ディレクターは そのビルダー・オブジェクトをその後のすべての構築で使用します それに代わるやり方として ディレクターの特定プロダクト用構築メソッドにビルダーを直接渡すということもできます

  6. すべてのプロダクトが同じインターフェースに従っている場合は 構築結果をディレクターから直接取得できます そうでない場合は クライアントはビルダーから結果を取得する必要します

長所と短所

  • 段階的にオブジェクトを作成したり 構築ステップを遅延させたり 再帰的にステップを実行することが可能
  • プロダクトの様々な表現の作成に際して 同じ構築コードの再利用が可能
  • 複雑な構築用コードを プロダクトのビジネス・ロジックから分離可能
  • 本パターンでは 複数の新規クラス作成の必要があるため コードの全体的な複雑さが増加

他のパターンとの関係

  • 多くの設計は まず比較的単純でサブクラスによりカスタマイズ可能な Factory Method から始まり 次第に もっと柔軟だが複雑な Abstract FactoryPrototypeBuilder へと発展していきます

  • Builder 複雑なオブジェクトを段階的に構築することに重点を置いています Abstract Factory 関連するオブジェクトの集団を作成することに特化しています Abstract Factory がすぐにプロダクトを返すのに対して Builder ではプロダクトの取得前に いくつかの追加の構築のステップを踏まなければなりません

  • Builder 複雑な Composite ツリー作成に使用できます 構築ステップを再帰的に行なうように プログラムします

  • BuilderBridge を組み合わせることができます ディレクター・クラスは抽象化層の役割を果たし ビルダーは実装です

  • Abstract Factories Builders Prototypes はどれも Singletons で実装可能です

コード例

Builder を C# で Builder を C++ で Builder を Go で Builder を Java で Builder を PHP で Builder を Python で Builder を Ruby で Builder を Rust で Builder を Swift で Builder を TypeScript で