gof-builder について
複雑なオブジェクトを組み立てる開発者向けに、GoF の Builder パターンを解説するスキルです。流れるようなビルダーと型安全な DSL を扱い、名前付き引数やデフォルト値を選ぶ場面も説明します。
主な機能
- 01段階的なオブジェクト構築
- 02流れるようなビルダー形式
- 03型安全な DSL ビルダー
- 04Builder と Factory の比較
- 05構築時のオブジェクト検証
活用シーン
- 01コンストラクターに省略可能な引数が多い場合
- 02複数段階でオブジェクトを組み立てる場合
- 03呼び出し側で組み立て手順を明確にしたい場合
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yonatankarp/ software-design-skills --skill gof-builderインストール後、Claude Code で/gof-builderを入力して読み込まれたか確認します。
詳細
1 行の概要
複雑なオブジェクトの構築と表現を分離します。同じ構築プロセスで異なる結果を生成でき、「テレスコーピングコンストラクター」を、流暢で読みやすい組み立て方に置き換えます。
このスキルを使う場面
- オブジェクトのコンストラクターパラメーターが多く(およそ 4 個以上)、そのうちいくつかが省略可能です。
- 構築が段階的に進み(バーガーを作り、次にトッピングを追加し、それから包むなど)、手順が変わる場合があります。
- 呼び出し側のコードを、コンストラクターのシグネチャに悩まされるのではなく、レシピのように読めるようにしたい場合です。
- Kotlin の DSL イディオムである
html { head { … } body { … } }は Builder です。
このスキルを使わない場面
- コンストラクターのパラメーターが 1~3 個で、単一の呼び出しが明確で読みやすい場合です。
- 言語にもっと適した手段がすでにある場合です。Kotlin では、名前付き引数とデフォルトパラメーター値を使えば、ほとんどの場合 Builder は不要です。Builder を使うのは、構築が本当に段階的な場合(中間検証や、型安全な手順の順序付け)に限ります。
- 単一の不変値オブジェクトを構築する場合です。名前付き引数を使う
data classでほとんどの場合は十分です。
Factory(gof-factory)より Builder を選ぶ場面
- Factory は、どの具象型をインスタンス化するかを隠します。
- Builder は、型がわかっているインスタンスをどのように組み立てるかを隠します。
両者は組み合わせて使えます。Factory が Builder を返すことも、Builder が部分オブジェクトのために Factory を使うこともできます。
主な内容
よく使われる形式は 2 つあります。
流暢な Builder。 Builder クラスが、連鎖可能なセッターメソッドで状態を蓄積し、最後に build() を呼び出します。
Burger.builder()
.bun(Brioche)
.patty(Beef)
.topping(Cheese).topping(Bacon)
.build()
各セッターは、連鎖できるように this(または Builder)を返します。build() は検証を行い、完成したオブジェクトを返します。呼び出し側が必ず Builder を経由するように、対象型のコンストラクターは private にする必要があります。
型安全な DSL Builder(Kotlin のイディオムですが、概念自体は以前からあります)。レシーバー付きのラムダを受け取る関数を使います。ラムダ内では、Builder のメソッドをスコープ内で呼び出せます。Kotlin 標準ライブラリの buildString { append("hello"); append(" world") } が最もシンプルな例です。HTML 形式の Builder は、この仕組みをさらに発展させています。
よくある間違いは、本来不要なオブジェクトに Builder を実装することです。構築が単一のコンストラクター呼び出しでは意図がわかりにくくなるほど複雑な場合に、Builder を使う価値があります。
判断の目安
- コンストラクターパラメーターの数を数えます。およそ 4 個未満なら、Builder は過剰設計です。名前付き引数を使いましょう。
- 妥当なデフォルト値を持つ省略可能なパラメーターがないか確認します。大半のパラメーターにデフォルト値がある場合、名前付き引数とデフォルト値を使うほうが Builder より読みやすくなります。
- 構築に複数の段階が必要で、段階 N が段階 N-1 に依存する場合は、Builder(特に型状態を使うもの)がその複雑さに見合います。
- 対象オブジェクトを不変にする場合、
build()は完全に検証された値を生成し、Builder 自体は破棄する必要があります。
アンチパターン
- 名前付き引数で済む場面で Builder を使うこと。利点がないのに手続きだけが増えます。
build()が無効なオブジェクトを返せる Builder。安全性という目的に反します。- 構築したオブジェクトへの可変参照を保持し、
build()の後もそのオブジェクトを変更し続ける「Builder」クラス。 - 1 つの Builder インスタンスを複数スレッドで共有すること。Builder は通常、状態を持つためスレッドセーフではありません。
関連項目
gof-factory— Builder は組み立て方を隠し、Factory は型の選択を隠します。両者は組み合わせて使えます。gof-prototype— 既存のインスタンスを出発点にするほうが簡単な場合に、Builder の代替となります。gof-kotlin-idioms— ほとんどの Kotlin コードでは、名前付き引数とデフォルト値が Builder の代わりになります。主な例外は型安全な Builder DSL です。
参考資料
- Design Patterns(GoF、1994 年)、第 3 章 — Builder。(HFDP 第 2 版には含まれず、簡単に触れられているだけです。)
- Kotlin Design Patterns and Best Practices(Alexey Soshin)、第 2 章 — Builder。Kotlin の名前付き引数との明示的な比較があります。