Abstract Factoryパターンとは
Abstract Factoryパターンは、Factory of Factoriesと呼ばれており、クライアントが抽象クラスを使って、具体的な製品を知ることなく、製品を作成することができます。
Abstract Factoryパターンの使い方
Factoryパターンで示した例をもとに考えていきます。
Factoryパターンでは、FactoryクラスがGyudonクラスを継承したオブジェクトを生成し、返却していました。この場合、Gyudonクラスで使用する食材(BeefやRiceなど)は各クラス毎に設定することができます。つまり、店舗毎に使う食材を決めることができてしまいます。管理する側としては、店舗毎に食材の品質が変わることは好ましくないので、どうにか対処しなくてはなりません。そこでAbstractFactoryパターンを使います。AbstractFactoryパターンでは、使う一連の食材を定義しておき、その定義されたクラスを使えば、製品ができてしまうというものです。管理する側も定義されている食材しか使われないため、管理が容易になります。
実装を見ていきましょう。
日本の店舗であるJapaneseGyudonStoreクラスは、日本の店舗で使用する食材一覧が定義されているJapaneseGyudonIngredientFactoryクラスを使います。返却するGyudonオブジェクト生成時にこのFactoryオブジェクトを渡すことで、Gyudonが使う食材を渡すことができます。
public class JapaneseGyudonStore extends GyudonStore {
public Gyudon createGyudon(GyudonTypeEnum type) {
Gyudon gyudon = null;
GyudonIngredientFactory factory = new JapaneseGyudonIngredientFactory();
if (type.equals(GyudonTypeEnum.NORMAL)) {
gyudon = new NormalGyudon(factory);
gyudon.setName("Japanese Normal Gyudon");
} else if (type.equals(GyudonTypeEnum.CHEESE)) {
gyudon = new CheeseGyudon(factory);
gyudon.setName("Japanese Cheese Gyudon");
} else if (type.equals(GyudonTypeEnum.KIMCHI)) {
gyudon = new KimchiGyudon(factory);
gyudon.setName("Japanese Kimchi Gyudon");
}
return gyudon;
}
}
続いて、Gyudonクラスを継承したNormalGyudonクラスです。
NormalGyudonクラスは、GyudonIngredientFactoryインタフェースをフィールドで保有して使用しています。このことをコンポジションと言います。コンポジションとは、別クラスのオブジェクトを自分のクラスに取り込んで使うことを指します。
NormalGyudonクラスはコンストラクタ生成時に渡されたGyudonIngredientFactoryインタフェースを保持して、それをprepare()メソッドで使用しています。
public class NormalGyudon extends Gyudon{
GyudonIngredientFactory factory;
public NormalGyudon(GyudonIngredientFactory factory) {
this.factory = factory;
}
@Override
public void prepare() {
super.beef = factory.getBeef();
super.rice = factory.getRice();
super.cheese = factory.getCheese(UseOrNotEnum.NOT_USE);
super.kimchi = factory.getKimchi(UseOrNotEnum.NOT_USE);
}
}
JapaneseGyudonIngredientFactoryクラスは、GyudonIngredientFactoryインタフェースを実装しており、各食材の具象クラスを返却しています。このFactoryクラスは日本の店舗(JapaneseGyudonStoreクラス)からのみ使用されます。
public class JapaneseGyudonIngredientFactory implements GyudonIngredientFactory {
@Override
public Rice getRice() {
return new JapaneseRice();
}
@Override
public Beef getBeef() {
return new JapaneseBeef();
}
@Override
public Cheese getCheese(UseOrNotEnum enum1) {
if (UseOrNotEnum.USE.equals(enum1)) {
return new JapaneseCheese();
} else {
return new NoCheese();
}
}
@Override
public Kimchi getKimchi(UseOrNotEnum enum2) {
if (UseOrNotEnum.USE.equals(enum2)) {
return new JapaneseKimchi();
} else {
return new NoKimchi();
}
}
}
実行結果は以下の通りです。
public class GyudonAbstractFactoryTest {
public static void main(String[] args) {
// buy at Japanese shop
GyudonStore japan = new JapaneseGyudonStore();
japan.order(GyudonTypeEnum.NORMAL);
japan.order(GyudonTypeEnum.CHEESE);
japan.order(GyudonTypeEnum.KIMCHI);
// buy at Korean shop
GyudonStore korea = new KoreanGyudonStore();
korea.order(GyudonTypeEnum.NORMAL);
korea.order(GyudonTypeEnum.CHEESE);
korea.order(GyudonTypeEnum.KIMCHI);
}
}
cooked:Japanese Normal Gyudon
ingredient
beef: JapaneseBeef
rice: JapaneseRice
topping: No Cheese No Kimchi
++++++++++++++++++++
cooked:Japanese Cheese Gyudon
ingredient
beef: JapaneseBeef
rice: JapaneseRice
topping: JapaneseCheese No Kimchi
++++++++++++++++++++
cooked:Japanese Kimchi Gyudon
ingredient
beef: JapaneseBeef
rice: JapaneseRice
topping: No Cheese JapaneseKimchi
++++++++++++++++++++
cooked:Korean Normal Gyudon
ingredient
beef: KoreanBeef
rice: KoreanRice
topping: No Cheese No Kimchi
++++++++++++++++++++
cooked:Korean Cheese Gyudon
ingredient
beef: KoreanBeef
rice: KoreanRice
topping: KoreanCheese No Kimchi
++++++++++++++++++++
cooked:Korean Kimchi Gyudon
ingredient
beef: KoreanBeef
rice: KoreanRice
topping: No Cheese KoreanKimchi
++++++++++++++++++++
UML
Abstractパターンのメリット・デメリット
メリット
- 具象クラスに依存せず、抽象クラスやインタフェースに依存した設計
- 拡張が容易
デメリット
- クラス間の関係性が複雑で、規模が大きくなればなるほど可読性が低くなる
重要な設計原則
- 変化する部分をカプセル化する
- 継承よりコンポジションを好む
- 実装に対してではなく、インタフェースに対してプログラミングする
- 相互にやり取りするオブジェクト間には、疎結合設計を使用する
- クラスは、拡張に対しては開かれた状態であるべきだが、変更に対しては閉じた状態であるべきである
- 抽象に依存する。具象に依存してはならない
JDKによる実装例
- javax.xml.parsers.DocumentBuilderFactoryのnewInstance()メソッド
- javax.xml.transform.TransformerFactoryのnewInstance()メソッド
- javax.xml.xpath.XPathFactoryのnewInstance()メソッド
Reference
- Head First デザインパターン〜頭とからだで覚えるデザインパターンの基本
- techscore