DP-03: Factory Method パターン
オブジェクトの生成処理をサブクラスに委譲するパターンです。Collections.emptyList() やFiles.newBufferedReader() など、 Java 標準ライブラリでも広く使われています。
Factory Method パターンとは
Factory Method パターンは「オブジェクトを生成するためのインターフェースを定義しつつ、 どのクラスをインスタンス化するかはサブクラスが決める」パターンです。 親クラス(抽象ファクトリー)は「生成する」という流れだけを定義し、 具体的に何を生成するかはサブクラスに委ねます。
Simple Factory との違い
| 項目 | Simple Factory | Factory Method |
|---|---|---|
| 生成処理の場所 | 1つのクラスのメソッド(if-else で分岐) | サブクラスのオーバーライドメソッド |
| 拡張方法 | 既存クラスを修正する | 新しいサブクラスを追加する |
| GoF パターン | GoF 外(非パターン) | GoF の 23 パターンに含まれる |
Java 標準ライブラリの実例として、Collections.emptyList()(空の不変リスト)、Collections.singletonList()(1要素の不変リスト)、java.nio.file.Files.newBufferedReader()(ファイルを読み取る Reader の生成) などが Factory Method の考え方を採用しています。 呼び出し側は List や Reader といったインターフェース型だけを知れば足ります。
サンプルコード
Java 8 では抽象クラスと継承で Factory Method を実装します。createReport() が Factory Method で、サブクラスが具体的なクラスを生成します。呼び出し側は ReportFactory 型だけを知れば足ります。
よくあるミス・注意点
⚠️ Factory を使わず new で直接生成して依存が密結合になる
呼び出し側に new PdfReport() のような具体クラス名を直接書くと、 後でレポートの種類を増やしたり変更したりするときに呼び出し箇所を全て修正しなければなりません。 Factory を経由することで、呼び出し側は抽象型(インターフェース)だけに依存できます。
⚠️ Simple Factory と混同して if-else でクラスを分岐させる
Factory Method パターンの利点は「新しい種類を追加するときに既存クラスを修正しなくてよい」点です。 if-else での分岐(Simple Factory)は新種類追加のたびに既存コードの修正が必要になります。 これは開放閉鎖原則(拡張に対して開き、修正に対して閉じる)に反します。
⚠️ Collections.emptyList() の戻り値に要素を追加しようとする
Collections.emptyList() が返すリストは変更不可(immutable)です。 要素を追加しようとすると実行時に UnsupportedOperationException がスローされます。 変更可能なリストが必要なときは new ArrayList<>() を使いましょう。
テストする観点
PdfReportFactoryがPdfReportのインスタンスを生成すること(正しい型のオブジェクトが返ること)HtmlReportFactory・CsvReportFactoryも同様に正しい型を返すこと- 各ファクトリーの
process()が title と content を含む文字列を返すこと - PDF・HTML・CSV それぞれの出力形式(フォーマット)が正しいこと(境界値: title や content が空文字の場合)
- title に
nullを渡したとき NullPointerException がスローされること(境界値) - 呼び出し側が
ReportFactory型として扱ったとき、ポリモーフィズムが正しく機能すること