java-recipes

ホーム デザインパターン › DP-01

DP-01: Abstract Factory パターン

関連するオブジェクト族(ファミリー)をまとめて生成するインターフェースを提供するパターンです。 データベース接続のように「MySQL 用部品一式」「PostgreSQL 用部品一式」を切り替える場面でよく使われます。

Abstract Factory パターンとは

Abstract Factory(抽象ファクトリー)パターンは、関連する複数のオブジェクトを一まとまりで生成するための インターフェースを定義するパターンです。具体的なクラス名を直接書かずに、オブジェクト群を作成できます。

Abstract Factory パターンの特徴

  • 製品ファミリーの一貫性: MySQL 用の Connection と Statement は必ず MySQL 版が返るため、組み合わせミスが起きない
  • 切り替えの容易さ: ファクトリーを差し替えるだけで、クライアントコードを変えずに実装を変えられる
  • 依存関係の逆転: クライアントは抽象インターフェースだけに依存し、具体クラスを知らなくてよい

Factory Method との違い

  • Factory Method: 1種類のオブジェクトを生成するメソッドをサブクラスで実装する
  • Abstract Factory: 複数種類の関連オブジェクト(製品ファミリー)をまとめて生成するインターフェースを定義する

サンプルコード

AbstractFactoryPatternSample.java
public class AbstractFactoryPatternSample {

    // 抽象製品A: Connection インターフェース
    interface DbConnection {
        String connect();
    }

    // 抽象製品B: Statement インターフェース
    interface DbStatement {
        String executeQuery(String sql);
    }

    // 抽象ファクトリー: DB ファクトリーインターフェース
    interface DbFactory {
        DbConnection createConnection();
        DbStatement createStatement();
    }

    // 具体ファクトリー1: MySQL
    static class MySqlFactory implements DbFactory {
        @Override
        public DbConnection createConnection() {
            return new MySqlConnection();
        }
        @Override
        public DbStatement createStatement() {
            return new MySqlStatement();
        }
    }

    // 具体ファクトリー2: PostgreSQL
    static class PostgreSqlFactory implements DbFactory {
        @Override
        public DbConnection createConnection() {
            return new PostgreSqlConnection();
        }
        @Override
        public DbStatement createStatement() {
            return new PostgreSqlStatement();
        }
    }

    // 具体製品: MySQL 接続
    static class MySqlConnection implements DbConnection {
        @Override
        public String connect() {
            return "MySQL に接続しました(jdbc:mysql://localhost:3306/mydb)";
        }
    }

    // 具体製品: MySQL ステートメント
    static class MySqlStatement implements DbStatement {
        @Override
        public String executeQuery(String sql) {
            return "MySQL で実行: " + sql;
        }
    }

    // 具体製品: PostgreSQL 接続
    static class PostgreSqlConnection implements DbConnection {
        @Override
        public String connect() {
            return "PostgreSQL に接続しました(jdbc:postgresql://localhost:5432/mydb)";
        }
    }

    // 具体製品: PostgreSQL ステートメント
    static class PostgreSqlStatement implements DbStatement {
        @Override
        public String executeQuery(String sql) {
            return "PostgreSQL で実行: " + sql;
        }
    }

    // クライアントコード: ファクトリーを使って DB 操作
    static void runDbOperation(DbFactory factory) {
        DbConnection connection = factory.createConnection();
        DbStatement statement = factory.createStatement();
        System.out.println(connection.connect());
        System.out.println(statement.executeQuery("SELECT * FROM users"));
    }

    public static void main(String[] args) {
        System.out.println("=== MySQL ===");
        runDbOperation(new MySqlFactory());

        System.out.println("\n=== PostgreSQL ===");
        runDbOperation(new PostgreSqlFactory());
    }
}

Java 8 では interface と static な内部クラスで抽象ファクトリーを実装します。クライアントコード(runDbOperation メソッド)は DbFactory インターフェースだけに依存しており、MySQL と PostgreSQL のどちらを渡しても同じコードで動作します。

よくあるミス・注意点

⚠️ 新しい製品を追加するとすべてのファクトリーを修正する必要がある

Abstract Factory インターフェースに新メソッドを追加すると、 すべての具体ファクトリークラス(MySqlFactory、PostgreSqlFactory など)を修正しなければなりません。 製品の種類が頻繁に増える場合は設計を見直しましょう。

⚠️ Factory Method と混同してしまう

Factory Method は「1種類のオブジェクトをサブクラスで作り分ける」パターンです。 Abstract Factory は「複数の関連オブジェクトをまとめて作る」パターンです。 1つのメソッドしかない Abstract Factory は Factory Method と同じになってしまうため、 複数の製品を一貫して生成する場面で選択しましょう。

⚠️ クライアントコードで具体クラスを直接使ってしまう

new MySqlConnection() のように具体クラスを直接インスタンス化すると、 Abstract Factory パターンの恩恵がなくなります。 クライアントコードは必ず抽象インターフェース(DbFactoryDbConnection など)だけを参照するようにしましょう。

テストする観点

  • MySqlFactory が返す Connection は MySQL 用の接続文字列を含むこと
  • PostgreSqlFactory が返す Connection は PostgreSQL 用の接続文字列を含むこと
  • MySqlFactory が返す Statement は MySQL 用の SQL 実行メッセージを返すこと
  • 同じファクトリーから生成した Connection と Statement は同じ DB 種別(製品ファミリーの一貫性)であること
  • クライアントコード(runDbOperation)は DbFactory インターフェースを受け取るため、MySqlFactory と PostgreSqlFactory のどちらでも正常動作すること(ポリモーフィズムの確認)
  • executeQuery に null を渡したとき NullPointerException が発生しないこと(境界値)

GitHub でソースコードを見る →