DP-02: Builder パターン
コンストラクタの引数が増えすぎる「テレスコーピング問題」を解決するパターンです。java.net.http.HttpRequest.Builder など、 標準ライブラリでも広く使われています。
Builder パターンとは
オブジェクトのフィールドが多くなると、コンストラクタの引数も増えていきます。 引数が5つ・6つと増えると「何番目の引数が何か」がわかりにくくなり、 引数の順番を間違えてもコンパイルエラーにならないというバグが生まれます。 これを「テレスコーピングコンストラクタ問題」と呼びます。
Builder パターンの特徴
- 名前付き設定:
.method("POST")のように何を設定しているか一目でわかる - 必須フィールドの強制: Builder のコンストラクタで必須フィールドを受け取り、忘れをコンパイル時に防ぐ
- デフォルト値の管理: オプションフィールドには Builder 側でデフォルト値を持てる
- イミュータブルオブジェクト:
build()後のオブジェクトは変更不可(final フィールド)にできる
標準ライブラリでは java.net.http.HttpRequest.Builder(Java 11+)、StringBuilder、ProcessBuilder などが Builder パターンを採用しています。
サンプルコード
Java 8 では Builder クラスを内部静的クラスとして実装します。必須フィールドはコンストラクタで受け取り、オプションフィールドはメソッドチェーンで設定します。
よくあるミス・注意点
⚠️ build() を呼び忘れる
メソッドチェーンを書いても最後に .build() を呼ばないと Builder オブジェクトが返り、目的のオブジェクトは生成されません。 IDE の型チェックでは気づきにくい場合もあるため注意しましょう。
⚠️ 必須フィールドをオプションにしてしまう
URL なしで HTTP リクエストを作るなど、意味をなさないオブジェクトが生成されてしまいます。 必須フィールドは Builder のコンストラクタで受け取るよう設計し、 null チェックも忘れずに入れましょう。
⚠️ Builder を再利用して複数オブジェクトを作るとフィールドが混ざる
同じ Builder インスタンスで .build() を複数回呼ぶと、 前の設定が残ったまま別のオブジェクトが作られることがあります。 原則として Builder は使い捨てにしましょう。
テストする観点
- 必須フィールド(URL)が null のとき
IllegalArgumentExceptionがスローされること - 必須フィールドが空文字のとき
IllegalArgumentExceptionがスローされること(境界値) - オプションフィールドを指定しない場合にデフォルト値が設定されること(例: method="GET", timeout=30000)
- タイムアウトに 0 や負の値を渡すと
IllegalArgumentExceptionがスローされること(境界値) - 同じ Builder を 2 回 build() しても同じ値を持つ独立したオブジェクトが返ること
- メソッドチェーンの順序を変えても同じ結果になること