Engineering
Glossvation Engineering
アルゴリズム

FactoryMethod パターン — Spring BeanFactory の背骨を自作する

インスタンス生成の種類を抽象側に閉じ込める Factory Method パターン。JDK/Spring の実例、Simple Factory との違い、現代 Java での書き方まで。

Glossvation ·

FactoryMethod パターン概念図

どういう問題を解くパターンか

ドメイン上で 「同じ目的のオブジェクトだが、生成方法が将来増える」 というときに、 new を呼び出し元から追い出し、生成責務を抽象クラスの createXxx() に委ねる設計です。

GoF 定義では 「インスタンス生成の判断を subclass に委譲する」 と表現されますが、 実務的な表現にすると以下のとおり:

  • 呼び出し側は Factory という抽象型に対して create() を呼ぶだけでよい
  • 具体的な new Account(...) / new Admin(...) の判断は concrete factory 側で行う
  • 新しい種類が増えても呼び出し側のコードは変更不要 (Open/Closed Principle)

いつ使う / 使わない

使うべき使わないほうがよい
生成する型が将来 2 種類以上に分岐しそう1 種類しかない (new で十分)
生成に前後処理 (登録・初期化) が必要Spring 等の DI が既に効いている
テストで差し替えたい (fake を注入したい)record + static factory method で足りる

Simple Factory との違い: switch 1 本で分岐する 「Simple Factory」 に対し、 Factory Method は 分岐そのものを型で表現 します。 switch を増やす代わりにクラスを増やすので、 分岐が頻繁に増える領域で効果が出ます。

実装

抽象プロダクト

public abstract class Product {
    public abstract void use();
}

抽象ファクトリ

生成の 骨格 (template) と、サブクラスに委ねる プリミティブ を分けるのが肝です。 create() は final にして、派生クラスが骨格を壊せないようにしておきます。

public abstract class Factory {

    /** 骨格: 生成→登録→返却の順序を固定する */
    public final Product create(String name) {
        Product product = createProduct(name);
        register(product);
        return product;
    }

    /** サブクラスが埋める差分 */
    protected abstract Product createProduct(String name);
    protected abstract void register(Product product);
}

具象プロダクトと具象ファクトリ

public class Account extends Product {
    private final String name;

    public Account(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public void use() {
        System.out.println("Account: " + name);
    }
}

public class AccountFactory extends Factory {
    private final List<String> names = new ArrayList<>();

    @Override
    protected Product createProduct(String name) {
        return new Account(name);
    }

    @Override
    protected void register(Product product) {
        names.add(((Account) product).getName());
    }

    public List<String> getNames() {
        return names;
    }
}

クライアント

呼び出し側は Factory 型に依存するだけ。 AccountFactory でも AdminFactory でも、どちらでも差し替え可能です。

public class Main {
    public static void main(String[] args) {
        Factory factory = new AccountFactory();
        factory.create("account01");
        factory.create("account02");
        factory.create("account03");
    }
}

JDK / Spring での実例

Factory Method は 「作って返す」 API のあちこちに潜んでいます。

  • java.util.Calendar#getInstance() — ロケール/タイムゾーンに応じた Calendar 実装を返す
  • java.nio.file.Paths#get(...) — プラットフォーム依存の Path を返す
  • java.util.Collections.unmodifiableList(...) — 内部実装クラスの UnmodifiableList を返す
  • Spring BeanFactory#getBean(...) — Bean 定義に応じて具体実装を返す- Spring LoggerFactory.getLogger(...) (SLF4J) — ランタイムの実装 (Logback/Log4j) を返す

落とし穴

1. 「Simple Factory」 との混同

switch で分岐するだけなら Factory Method ではなく Simple Factory。 Simple Factory で足りている箇所に無理に抽象クラスを足すとクラスが爆発します。

2. 骨格メソッドを final にしないと壊される

上記例の create(String) を final にしていないと、 サブクラスで 「ついでに」 オーバーライドされて 「生成→登録」 の順序が崩れがちです。 Template Method も同様の設計で final を推奨します。

3. パラメータが増えると引数の意味がぼやける

createProduct("name", "A", true, 10) のような呼び出しが出てきたら、 Builder (本連載の Builder パターン) との組み合わせを検討。

現代 Java での最小実装

分岐の軸が単純で、生成の前後処理も不要なら、 インタフェースの static ファクトリメソッド + sealed interface で十分です。

public sealed interface Product permits Account, Admin {
    static Product of(String type, String name) {
        return switch (type) {
            case "account" -> new Account(name);
            case "admin"   -> new Admin(name);
            default -> throw new IllegalArgumentException(type);
        };
    }
}

判断軸: 「生成の前後処理が複数ある」「生成器自体に状態を持たせたい」なら 古典的な Factory Method を、 「ただ型を切り替えたいだけ」なら上の最小実装で十分です。

抽象化は値段なので、支払う価値がある変更軸にだけ 使ってください。

関連記事