Abstract Factory パターン

Abstract Factory パターンの定義

具象クラスを指定することなく、一連の関連オブジェクトや依存オブジェクトを作成するためのインターフェースを提供する。

例えば

ある製品を作るのに、関東では以下の部品を組み合わせて使用する。

  • ProsuctA1
  • ProsuctB1

しかし関西では以下の部品を組み合わせて使用する。

  • ProsuctA2
  • ProsuctB2

この組み合わせは、地域毎で変わる可能性があるため、組み合わせを柔軟に設定できるようにしておきたい。
こんな時は Abstract Factory パターンの出番。

クラス構成

ファクトリ関連
パッケージ クラス 説明
factory Factory.java ファクトリーinterface ProductAとProductBのインスタンス化メソッドを定義
factory.impl FactoryImpl1.java ファクトリーinterface の具象クラス。Productの組み合わせをカプセル化している。例えば関東版のファクトリー
factory.imp FactoryImpl2.java ファクトリーinterface の具象クラス。Productの組み合わせをカプセル化している。例えば関西版のファクトリー
public interface Factory {
    ProductA createProductA();
    ProductB createProductB();
}

末尾が1のProductをインスタンス

public class FactoryImpl1 implements Factory {
    @Override public ProductA createProductA() {
        return new ProductImplA1();
    }
    @Override public ProductB createProductB() {
        return  new ProductImplB1();
    }
}

末尾が2のProductをインスタンス

public class FactoryImpl2 implements Factory {
    @Override public ProductA createProductA() {
        return new ProductImplA2();
    }
    @Override public ProductB createProductB() {
        return new ProductImplB2();
    }
}
プロダクト関連
パッケージ クラス 説明
product ProductA.java 部品用のinterface。部品A
product ProductB.java 部品用のinterface。部品B
product.Impl ProductImplA1.java 部品用のinterfaceの実装クラス。部品A1
product.Impl ProductImplA2.java 部品用のinterfaceの実装クラス。部品A2
product.Impl ProductImplB1.java 部品用のinterfaceの実装クラス。部品B1
product.Impl ProductImplB2.java 部品用のinterfaceの実装クラス。部品B2
public interface ProductA {
    String getName();
}
public interface ProductB {
    String getId();
}
public class ProductImplA1 implements ProductA {
    @Override public String getName() {
        return "ProsuctA1";
    }
}
public class ProductImplA2  implements ProductA {
    @Override public String getName() {
        return "ProsuctA2";
    }
}
public class ProductImplB1 implements ProductB {
    @Override public String getId() {
        return "B1";
    }
}
public class ProductImplB2 implements ProductB {
    @Override public String getId() {
        return "B2";
    }
}
その他
パッケージ クラス 説明
- Client.java 部品を組み立てる主体となるクラス
- UnitTest.java 動作確認用クラス

外部から与えられるファクトリからインスタンスを取得し、処理を行う。

public class Client {
    private Factory factory;
    public Client(Factory factory) {
        this.factory = factory;
    }
	
    public void execute() {
        ProductA prosuctA = factory.createProductA();
        ProductB prosuctB = factory.createProductB();
        System.out.println(prosuctA.getName() + ":" + prosuctB.getId());
    }
}

以下はテストメソッド

    @Test public void tear1() throws Exception {
        Factory factory = selectFactory(1);
        Client client = new Client(factory);
        client.execute();
    }

    @Test public void tear2() throws Exception {
        Factory factory = selectFactory(2);
        Client client = new Client(factory);
        client.execute();
    }

    private Factory selectFactory(int key) {
        switch (key) {
        case 1: return new FactoryImpl1();
        case 2: return new FactoryImpl2();
        default: throw new RuntimeException("no target");
        }
    }

実行結果

ProsuctA1:B1
ProsuctA2:B2

それぞれProsuctのセットがファクトリで指定した通りに出力されている。Abstract Factoryにしておくと以下の恩恵が得られる。

  • A1とB2の組み合わせを増やしたい場合、ファクトリを追加するのみで良い(UnitTestの修正は必要だが)
  • A1をC1に変更したい場合、ProsuctC1を追加して、ファクトリのFactoryImpl1を変更するのみで済む

なんでいまさら

なんでいまさらこんな事を書いてるかというと、デザインパターンの説明って、だらだら文章書くよりシンプルなコード自体を見る方が分かりやすいのではないかと思ったから・