#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 問のクイズで、 このパターンを身体に染み込ませよう。