DPDesign Pattern Quest
#04 / 23
C生成DIFF

ビルダー

Builder

サブウェイの注文だ。『パン → 具 → ソース → トッピング』と段階を踏み、最後に「いただきます」で完成。

Intent · 目的

オブジェクトの組み立て手順と、最終的にできあがる中身を分離する。同じ手順で違う製品を作れて、しかも引数が10個あろうがコードは綺麗なままにできる。

Motivation · 動機

「コンストラクタ引数12個」の関数を見たことがあるだろうか。`new HttpRequest(url, "POST", null, headers, true, null, false, ...)` ── 真ん中の `null` と `false` が何の意味か、誰も覚えていない。Builder を使えば `.method("POST").header(...).body(...)` と人間語で組めて、必要なオプションだけ書ける。サブウェイで「ハム、レタス、オニオン、マスタード、ピクルス少なめ」と頼むのと同じ感覚。

適用場面

  • 1オプション項目が多い不変オブジェクト(HTTPリクエスト、SQL クエリ、フォームデータ)。
  • 2同じ手順で異なる『最終形』を作りたい場合(同じ JSON データから HTML / Markdown / CSV を生成)。
  • 3段階的・条件分岐的に組み立てる場合(あれば追加、なければスキップ)。

概念図

サンプルコード

interface HttpRequest {
  readonly url: string;
  readonly method: string;
  readonly headers: Readonly<Record<string,string>>;
  readonly body: string;
}

class HttpRequestBuilder {
  private _method = "GET";
  private _headers: Record<string,string> = {};
  private _body = "";

  constructor(private readonly url: string) {}

  method(m: string) { this._method = m; return this; }
  header(k: string, v: string) { this._headers[k] = v; return this; }
  body(b: string) { this._body = b; return this; }
  build(): HttpRequest {
    return Object.freeze({
      url: this.url,
      method: this._method,
      headers: { ...this._headers },
      body: this._body,
    });
  }
}

const req = new HttpRequestBuilder("https://api.example.com")
  .method("POST")
  .header("Content-Type", "application/json")
  .body(JSON.stringify({ id: 1 }))
  .build();
Pros · メリット
  • コンストラクタが何個に膨らんでも、呼び出し側は人間に読める形で書ける。
  • 途中で `if 必要なら .header(...)` のような条件付き組立てが自然にできる。
  • 不変オブジェクト(imutable)と相性◎。設定が固まった瞬間に `build()` で凍結。
Cons · デメリット
  • ビルダー用のクラスが1つ増える。引数2〜3個のクラスに当てると過剰装備。
  • build() を呼び忘れると未完成のビルダーが流通する。型で強制するには工夫がいる。
  • 言語によっては記述量が増える(Java だと特に)。

関連パターン

⚔ ready to test

理解度を確かめる時間だ

3 問のクイズで、 このパターンを身体に染み込ませよう。

挑戦する →