DP-04: Prototype パターン
既存のオブジェクトを雛形として複製し、新しいインスタンスを作るパターンです。 コスト高な初期化を繰り返さず、雛形から複製することで効率的にオブジェクトを生成できます。
Prototype パターンとは
Prototype(プロトタイプ)パターンは、すでに存在するオブジェクトを「雛形(プロトタイプ)」として使い、 そのコピーから新しいオブジェクトを作るパターンです。 毎回ゼロからオブジェクトを生成するよりも、雛形を複製して必要な部分だけ変更するほうが コードが簡潔になる場面で役立ちます。
Prototype パターンが役立つ場面
- 雛形からの派生: 「標準注文」を雛形にして「急便注文」「まとめ発注」を作るような場面
- 設定の共通化: 共通の設定値を持つオブジェクトを複数作りたい場面
- イミュータブル設計との相性: Java 17+ の record では withXxx() スタイルのコピーが自然に書ける
シャローコピーとディープコピーの違い
- シャローコピー(浅いコピー): 参照型フィールドはポインタだけコピーされる。元オブジェクトと複製が同じリストを共有するため、どちらを変更しても両方に影響が出る
- ディープコピー(深いコピー): 参照型フィールドも新しく作り直す。元オブジェクトと複製は完全に独立している
サンプルコード
Java 8 ではコピーコンストラクタ(コピー用の別コンストラクタ)を定義するのが最も安全な方法です。Object.clone() は Cloneable の実装が必要なうえ、参照型フィールドのシャローコピー問題があるため推奨されません。
よくあるミス・注意点
⚠️ シャローコピーで List や Map が共有されてしまう
Object.clone() はシャローコピーです。 フィールドに List や Map などの参照型が含まれる場合、コピー元とコピー先が同じオブジェクトを参照します。 コピー先に要素を追加するとコピー元にも反映されてしまいます。 コピーコンストラクタで new ArrayList<>(other.notes) のように 新しいリストを作りましょう。
⚠️ Object.clone() は Java では非推奨
Cloneable インターフェースとObject.clone() の組み合わせは 設計上の欠陥があるとされています(Joshua Bloch 著「Effective Java」でも非推奨とされています)。 Java で Prototype パターンを実装する際は「コピーコンストラクタ」か 「コピーファクトリーメソッド(static な copy() メソッド)」を使うのが推奨されています。
⚠️ record の wither パターンでネストした record を忘れる
Java 17+ の record で withXxx() を定義するとき、フィールドにネストした record や List がある場合は それも新しいインスタンスに差し替える必要があります。 List フィールドには List.copyOf() を使って イミュータブルなコピーを渡しましょう。
テストする観点
- コピーコンストラクタで作ったオブジェクトが元のオブジェクトと同じフィールド値を持つこと
- コピー後に元オブジェクトを変更しても、コピー先のフィールド値が変わらないこと(ディープコピーの確認)
- コピー後にコピー先の notes に要素を追加しても、元オブジェクトの notes が変化しないこと(List の独立性)
- quantity に 0 を設定してコピーしたとき、コピー先の quantity が 0 であること(境界値)
- notes が空のオブジェクトをコピーしても、コピー先の notes が空のリストであること(境界値)
- 雛形から複数回コピーしたとき、それぞれが独立したオブジェクトであること