概要

オブジェクトの生成を呼び出し側に直接 new させると、具象クラスへの依存が広がり、出力形式の追加や切り替えのたびに修正箇所が増えます。Factory Method パターンは、生成処理をサブクラスやメソッドに委譲することで、呼び出し側は Product インターフェースだけを知っていれば済む構造を作ります。この記事では、帳票出力(PDF・HTML・CSV)を題材に Factory Method の基本構造を示し、Java 標準ライブラリの Collections.emptyList や List.of が同じ考え方で設計されていることも確認します。Abstract Factory や Builder との使い分け基準も整理するので、パターン選択で迷ったときの判断材料にしてください。

使いどころ

帳票の出力形式(PDF / HTML / CSV)を設定値で切り替え、出力ロジックを呼び出し側から隠蔽する

通知チャネル(メール / Slack / SMS)をファクトリーで切り替え、送信処理の追加時に既存コードを変更しない

テスト時にモックオブジェクトを返すファクトリーに差し替え、外部依存なしでユニットテストを実行する

コード例

FactoryMethodSample.java
import java.util.List;

public class FactoryMethodSample {

    // Product インターフェース
    interface Report {
        String generate(String title, String content);
    }

    // 具象 Product
    static class PdfReport implements Report {
        @Override
        public String generate(String title, String content) {
            return "[PDF] " + title + ": " + content;
        }
    }

    static class CsvReport implements Report {
        @Override
        public String generate(String title, String content) {
            return "\"" + title + "\",\"" + content + "\"";
        }
    }

    // Creator 抽象クラス
    static abstract class ReportFactory {
        protected abstract Report createReport();

        public String process(String title, String content) {
            var report = createReport();
            return report.generate(title, content);
        }
    }

    // 具象 Creator
    static class PdfReportFactory extends ReportFactory {
        @Override
        protected Report createReport() { return new PdfReport(); }
    }

    static class CsvReportFactory extends ReportFactory {
        @Override
        protected Report createReport() { return new CsvReport(); }
    }

    public static void main(String[] args) {
        var factories = List.of(
            new PdfReportFactory(),
            new CsvReportFactory()
        );
        for (var factory : factories) {
            System.out.println(factory.process("月次売上", "100万円"));
        }

        // 標準ライブラリの Factory Method 例
        var list = List.of("Java 8", "Java 17", "Java 21");
        System.out.println("List.of: " + list);
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

var による型推論と record でファクトリーの呼び出しコードが簡潔になる。record をメタ情報の保持に使える。

Java 17
// Java 17: var + record でメタ情報を付与
var factory = new PdfReportFactory();
var report = factory.createReport();
record ReportType(String type, String title) {}
var meta = new ReportType("PDF", "月次レポート");

Library Comparison

標準 API(interface + サブクラス)生成対象の種類が限られ、切り替えロジックを自前で管理したいとき。生成対象が増えるとサブクラスも比例して増える。
Spring(@Bean / @Configuration)DI コンテナで Bean 生成を宣言的に管理したいとき。Spring 依存が前提になり、ライブラリ層では使いにくい。
MapStructDTO 変換の生成コードを自動化したいとき。Factory Method の設計思想とは用途が異なる。汎用の生成パターンには向かない。

注意点

ファクトリーのサブクラスが増えすぎると見通しが悪くなる。生成対象が少数なら static メソッドや enum ベースの簡易ファクトリーで十分な場合がある

Factory Method は生成する「1種類の Product」を切り替えるパターン。関連する複数の Product をまとめて生成するなら Abstract Factory を検討する

ファクトリーの戻り値型を具象クラスにすると委譲の効果が薄れる。必ず Product インターフェースで返すこと

FAQ

Factory Method と Abstract Factory の違いは何ですか。

Factory Method は1種類の Product の生成をサブクラスに委譲します。Abstract Factory は関連する複数の Product をまとめて生成するファクトリーのインターフェースを定義します。

static メソッドで生成するのと何が違いますか。

static ファクトリーメソッドは Simple Factory と呼ばれ、GoF の Factory Method とは別物です。サブクラスによるオーバーライドが不要なら static メソッドで十分です。

Java 標準ライブラリにも Factory Method はありますか。

Collections.emptyList()、List.of()、Calendar.getInstance() などが典型例です。呼び出し側は内部の実装クラスを意識せずにオブジェクトを受け取れます。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます