Engineering
Glossvation Engineering
アルゴリズム

AbstractFactory パターン — 「関連するオブジェクト群」を一式で切り替える

Factory Method との違いを 1 分で整理し、OS 別 UI / DB 別 Repository / テナント別実装など、実務での使いどころを示す。

Glossvation ·

AbstractFactory パターン概念図

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

互いに関連する複数の型のオブジェクトを、一貫した「ファミリー」として生成 するパターンです。

  • OS ごとに差し替える UI 部品 (Mac 用 Button + Mac 用 Dialog + Mac 用 Menu / Win 用 Button + …)
  • DB エンジン別の Repository 一式 (MySQL 用 UserRepo + MySQL 用 OrderRepo / Postgres 用 UserRepo + Postgres 用 OrderRepo)
  • SaaS でテナントごとに差し替える実装一式

Factory Method が 「1 種類の抽象プロダクト」 を扱うのに対し、 Abstract Factory は 「複数種類の抽象プロダクトを束ねて 1 ファミリー」 として扱います。

Factory Method との違い (1 分で理解)

Factory MethodAbstract Factory
生成する型の数1 種類複数種類 (関連を持つ)
バリエーションの単位型ごとファミリーごと
典型例Logger.of(name)UiToolkit.forOs(OS.MAC)
クラス数少ない多い (型 × バリエーション分)

判断軸: 作りたいオブジェクトが 互いに協調して動く必要がある (Mac Button と Mac Dialog を混ぜて使う) なら Abstract Factory。それ以外は Factory Method で足ります。

いつ使う / 使わない

使うべき使わないほうがよい
複数の型を 「ファミリー」 として切り替える単一型の生成
異なるファミリーの実装を混ぜると壊れる混ぜても壊れない
バリエーション (OS/DB/テナント) が固定数バリエーションが頻繁に増える

バリエーションが頻繁に増える環境では、 ファミリーの数だけファクトリクラスを増やし続ける 必要があり、 クラス爆発を招きがちです。 その場合は 「設定駆動の DI」 (Spring の @Profile や @ConditionalOnProperty) に寄せた方が楽です。

実装: UI ツールキットの例

抽象プロダクトを 2 つ (Button, Dialog)、 バリエーションを 2 つ (Mac, Windows) で考えます。

抽象プロダクト

public interface Button {
    void render();
}

public interface Dialog {
    void show(String message);
}

Mac ファミリー

final class MacButton implements Button {
    @Override public void render() {
        System.out.println("[Mac] rendering rounded button");
    }
}

final class MacDialog implements Dialog {
    @Override public void show(String message) {
        System.out.println("[Mac] sheet: " + message);
    }
}

Windows ファミリー

final class WinButton implements Button {
    @Override public void render() {
        System.out.println("[Win] rendering square button");
    }
}

final class WinDialog implements Dialog {
    @Override public void show(String message) {
        System.out.println("[Win] modal: " + message);
    }
}

抽象ファクトリと具象ファクトリ

public interface UiToolkit {
    Button createButton();
    Dialog createDialog();

    static UiToolkit forOs(Os os) {
        return switch (os) {
            case MAC     -> new MacUiToolkit();
            case WINDOWS -> new WinUiToolkit();
        };
    }
}

final class MacUiToolkit implements UiToolkit {
    public Button createButton() { return new MacButton(); }
    public Dialog createDialog() { return new MacDialog(); }
}

final class WinUiToolkit implements UiToolkit {
    public Button createButton() { return new WinButton(); }
    public Dialog createDialog() { return new WinDialog(); }
}

public enum Os { MAC, WINDOWS }

クライアント

呼び出し側は ファクトリ 1 つだけ受け取れば、ファミリー全体を正しく使える。 Mac と Windows の実装を混ぜる事故が起きません。

public final class App {
    private final UiToolkit toolkit;

    public App(UiToolkit toolkit) {
        this.toolkit = toolkit;
    }

    public void run() {
        toolkit.createButton().render();
        toolkit.createDialog().show("ようこそ");
    }

    public static void main(String[] args) {
        new App(UiToolkit.forOs(Os.MAC)).run();
    }
}

JDK / Spring での実例

  • java.sql.DriverManager + Connection/Statement/ResultSet — ドライバごとに 関連する複数型 を返す。実質的な Abstract Factory
  • javax.xml.parsers.DocumentBuilderFactory — パーサ実装ファミリ (Xerces, Crimson…) を一式切り替え
  • Spring の @Profile + @Configuration — 本番/開発/テスト別に Bean ファミリをまるごと差し替え- AWS SDK v2 の DynamoDbClientBuilder, S3ClientBuilder などリージョン別の実装群 Spring を使っている場合、@Profile("prod") を付けた @Configuration を用意すれば、 UserRepository, OrderRepository, PaymentGateway などを一括で 「本番ファミリ」 に切り替えられます。 これが実質的な Abstract Factory 相当の役割を果たします。

落とし穴

1. 新しいプロダクト型を追加するコストが高い

抽象ファクトリに createXxx() を追加すると、既存の全具象ファクトリが修正対象 になります。 Open/Closed Principle が崩れるので、プロダクト型は最初によく設計する必要あり。

2. バリエーション間でメソッドシグネチャが揃わない

OS A のダイアログはモーダル専用、OS B のダイアログは非モーダル対応…など、 現実には抽象インタフェースに揃えづらい差分が出てきます。 無理に抽象化するより、Strategy や Visitor に逃がす ほうがきれいなことも。

3. DI コンテナを使っているなら、まず @Profile を検討

モダンな Java/Spring では、Abstract Factory を手書きする代わりに プロファイルや条件付き Bean で切り替える のが読みやすい場合が多い。 手書き Abstract Factory が本当に必要か、一度立ち止まる価値があります。


まとめ: Abstract Factory は 「ファミリーの一貫性」 を型で守るための強いパターン。 ただし適用先は限定的で、Spring の @Profile などに寄せたほうが現代的なことも多い。 手書きで入れるのは、フレームワーク非依存のライブラリ や、 実行時に動的に切り替えたいケース を中心に。

関連記事