Home: Up: Back

Abstract Factory

あるオブジェクト群のクラス毎に関連付けたい別のオブジェクト群を外部から提供する。
関連付けたいオブジェクト群は実行時に切り替えることができる。


オブジェクト集合Aの各ノード毎に対応する別の集合Bがあるとき、集合Aに対して、一対一または集合Aの一クラスにつき一つのオブジェクトで集合Bを関連づけて生成する事ができます。
集合Aのクラス群は変更不要となり、集合Bとして新たなオペレーションを加えた別の集合を使用することも可能になります。

// 集合Aの各クラスが実装するinterface
interface Client {
  Product createProduct(AbstractFactory factory);
// または
//void setFactory(AbstractFactory factory);
//Product createProduct();
}

// 集合Bの各クラスに最低限実装させるオペレーション群
// 空インターフェイスでもよい
interface Product {
  void operation();
}
// abstract factory インターフェイス
// 集合Aの各クラスが呼び出すためのfactory method群を定義
// 集合Bの実際のクラスを決定するのは、これを実装したクラスになる。
// 集合Aに含まれるクラスが増えた場合、それに対応するfactory method
// を追加することになる。
interface AbstractFactory {
  Product createB_1();
  Product createB_2();
    :
}
// 以下、集合Aのクラスの実装例。こちらはこれで変更不要。
// クラス追加の場合はAbstractFactoryにメソッドを加えて
// それを呼び出すような実装を書く。
class A_1 implements Client {
  public Product createProduct(AbstractFactory factory) {
    return factory.createB_1();
  }
    :
}
class A_2 implements Client {
  public Product createProduct(AbstractFactory factory) {
    return factory.createB_2();
  }
    :
}
// 以下、集合Bを定義する例。
// 集合Bの各クラスに取りつけたい抽象インターフェイス
// そのようなものが必要ない場合はProductを
// implementsしたクラス群を用意すればよい。
interface ExtendedProduct extends Product {
  void otherOperation();
}
class B_1Impl1 implements ExtendedProduct {
  public void operation() {
    // B_1としての実装
  }
  public void otherOperation() {
    // B_1としての実装
  }
}
class B_2Impl1 implements ExtendedProduct {
  public void operation() {
    // B_2としての実装
  }
  public void otherOperation() {
    // B_2としての実装
  }
}
// ★★ factory クラス ★★
class FactoryImpl1 implements AbstractFactory {
  public Product createB_1() {
    return new B_1Impl1();
  }
  public Product createB_2() {
    return new B_2Impl1();
  }
    :
}
// 上記のセットを新たに用意することもできる
// 例: B_1Impl2 / B_2Impl2 / FactoryImpl2 といった具合
// そして、上記オブジェクトの集合を扱うアプリケーションの例
//   ↓にはAの実装クラスがたくさん入ってるとして。
A[]  collectionA = new A[100];
  :
AbstractFactory factory = new FactoryImpl1();
ExtendedProduct b;
for(int i = 0; i < collectionA.length; i++) {
  // AbstractFactoryImpl1の各メソッドがExtendedProductを返すことを知っているから
  b = (ExtendedProduct)collectionA[i].
                       createProduct(factory);
  b.operation();
  b.otherOperation();  // ExtendedProductのメソッドも呼べる
}
  :
補足
上記の方法では、createProduct(AbstractFactory)を呼ぶ毎にnewが行われてしまって効率が悪いです。
そうせざるを得ない場合もあるのですが、以下のようにすればいくらか効率が良くなります。
class FactoryImpl3 implements AbstractFactory {
  private Product b_1Impl3;
  private Product b_2Impl3;
    :
  public Product createB_1() {
    if (b_1Impl3 == null) b_1Impl3 = new B_1Impl3();
    return b_1Impl3_;
  }
  public Product createB_2() {
    if (b_2Impl3 == null) b_2Impl3 = new B_2Impl3();
    return b_2Impl3;
  }
}
要はConcreteFactoryの中でオブジェクトをキャッシュするわけですね。こうすると集合Aの同一のクラスの複数のオブジェクトに対して、集合Bの同一のオブジェクトが関連付けられることになるので、一対一であることが必然の場面では使えません。
また、ConcreteFactoryオブジェクトを一度作ったら使いまわすようにしないとキャッシュの効果は現れません。
★そのためにConcreteFactoryクラスをSingletonにしたりします。
キャッシュすべき個所を変えたりすることでさらに効率を良くすることができます。
かなり例が特異なものになってしまった・・・。

もっと典型的な(正しいというべきか?)使い方は、GoF本にあるように既存のクラスツリーに対応する入れ換え可能な別のクラスツリーの生成手段を与えているということなんですけど・・・日本語がややこしくて解りにくそうですねえ。

awtのToolkitでのAbstract Factory

ありきたりな説明を書いておくと、awtはjava.awt.Componentを基準(親)としてたくさんの部品を持っていますが、それらの部品の実装はそれぞれがプラットフォーム毎に異なります。

これを分離して、各部品のプラットフォーム毎に異なる実装部分を〜Peerクラスで定義してComponentサブクラス群からはPeerの持っているプラットフォーム毎に異なる実装に対して委譲するようにしています(この部分はBridgeパターンと呼ばれます。
(Peer interfaceだけに着目すればStrategyパターンともいえるのでは?)

そして、このような構造を作り出すためにAbstract Factoryパターンが用いられています。

awtでのabstract factoryはjava.awt.Toolkitクラスです。
各プラットフォームではそれぞれに応じたToolkitのサブクラスが用意されています。
そして、Toolkit#getDefaultToolkit()といったメソッドで、システムプロパティに記述された、プラットフォーム毎に異なるToolkitサブクラスを生成します。

各部品のPeerは上記のようにして得られたToolkitサブクラスのcreate〜()メソッドによって生成されます。例えば、ボタンはcreateButton()メソッドです。
createButton()で生成されるのはButtonPeerのサブクラスであり、実際の型はプラットフォーム毎に異なるというわけです。

これがAbstract Factoryパターンによって、Componentから派生したクラス(部品)の集合に一対一で対応するPeerクラスが関連づけられる仕組みです。