8. AbstractFactory パターン
- 2012/04/26 一部修正しました
- 8.1 AbstractFactoryパターンとは
- 8.2 サンプルケース
- 8.3 AbstractFactory パターンのまとめ
8.1 AbstractFactoryパターンとは
第8章では、 AbstractFactory パターンを学びます。AbstractFactory を日本語に直訳すると「抽象的な工場」となります。いったい抽象的な工場とは、どんな工場なのでしょう。AbstractFactory パターンとは、インスタンスの生成を専門に行うクラスを用意することで、整合性を必要とされる一連のオブジェクト群を間違いなく生成するためのパターンです。
例えば、「車」を作成するプログラムを考えてみてください。このプログラムのある部分で、「車」オブジェクトである変数 car にタイヤとハンドルを追加しています。
car.addTire(new CarTire()); car.addHandle(new CarHandle());
このプログラムの書き方は、たいていの場合問題ないのですが、プログラマーのふとしたミスで、とんでもない間違いが起きる可能性があります。例えば、車を作っているのに、自転車のタイヤを渡してしまうようなことを考えて見ましょう。
car.addTire(new BicycleTire()); car.addHandle(new CarHandle());
まさか実際に車を作成するときに、このような間違いを犯してしまうことは考えられません。しかし、利用すべきでないオブジェクトを利用してしまう可能性は大いに考えられます。このようなとき、車を作成するために必要な一連のオブジェクトを作成することを一手に引き受ける工場クラスを用意し、タイヤやハンドルといったオブジェクトを生成するときは、この工場クラスを利用してオブジェクトの生成を行うようにすることで、先ほどのような間違いが起こることを回避することができます。また、この工場クラスを変更することで、利用する一連のオブジェクトをごっそり変更することもできるようになります。
8.2 サンプルケース
サンプルケースでは、「鍋」を作ることを考えて見ます。水炊き、すき焼き、キムチ鍋、ちゃんこ鍋、石狩鍋、様々な鍋が考えられますが、おおよそ以下のパーツからなっていると考えられます。
- スープ
- メインの具(たんぱく質)
- 野菜
- その他の具
メインの具とは、肉や魚などのたんぱく質を指すものとします。鍋物をあらわす HotPot クラスは以下のように定義されているものとします。
import java.util.*; public class HotPot{ private Pot pot; private Soup soup; private Protein protein; private List vegetables; private List otherIngredients; public HotPot(Pot pot){ this.pot = pot; } public void addSoup(Soup soup){ this.soup = soup; } public void addMain(Protein protein){ this.protein = protein; } public void addVegetables(List<Vegetable> vegetables){ this.vegetables = vegetables; } public void addOtherIngredients(List<Ingredients> otherIngredients){ this.otherIngredients = otherIngredients; } }
コンストラクタの引数で、利用する鍋(容器として)を指定します。addSoup メソッドは、引数に Soup を取り、利用するスープを追加するものです。Soup クラスはここでは定義しませんが、スープをあらわすクラス全般の親クラスになるものと考えてください。次に、addMain メソッドですが、これは、引数に Protein オブジェクトを取ります。Protein クラスもここでは定義しませんが、Chicken や Beef 、Tofu クラスなどの親クラスになるものと考えてください。addVegitables メソッドは、引数に List を取り、「野菜」を追加するためのメソッドです。addOtherIngredients メソッドは、引数に List を取り、「その他の具」を追加するためのメソッドです。ジェネリクスで指定しているIngredientsクラスは、具のスーパークラス(Proteinクラス、Vegetableクラスのスーパークラス)と思ってください。
まずは、水炊きを作ってみましょう。
import java.util.*; public class SampleClass{ public static void main(String args[]){ HotPot hotPot = new HotPot(new Pot()); hotPot.addSoup(new ChickenBonesSoup()); // 鶏がらを煮込んだスープ hotPot.addMain(new Chicken()); // Main として鶏肉 List<Vegetable> vegetables = new ArrayList<Vegetable>(); vegetables.add(new ChineseCabbage()); // 白菜 vegetables.add(new Leek()); // ねぎ vegetables.add(new Chrysanthemum()); // 春菊 hotPot.addVegetables(vegetables); List<Ingredients> otherIngredients = new ArrayList<Ingredients>(); otherIngredients.add(new Tofu()); // 豆腐 hotPot.addOtherIngredients(otherIngredients); } }
これで水炊きの完成です。しかし、このようなプログラミング方法では、プログラマーによって、様々な水炊きが作成されることになります。何らかの理由でこれを防ぎたい場合があります。このような場合に、水炊きの具となるオブジェクトを専門に生成するクラスを用意し、水炊きを作るときにこのクラスからオブジェクトの生成を行うようにします。このクラスを、MizutakiFactory クラスと名づけましょう。MizutakiFactory クラスは、以下のようになります。
import java.util.*; public class MizutakiFactory extends Factory{ public Soup getSoup(){ return new ChickenBonesSoup(); } public Protein getMain(){ return new Chicken(); } public List<Vegetable> getVegetables(){ List<Vegetable> vegetables = new ArrayList<Vegetable>(); vegetables.add(new ChineseCabbage()); vegetables.add(new Leek()); vegetables.add(new Chrysanthemum()); return vegetables; } public List<Ingredients> getOtherIngredients(){ List<Ingredients> otherIngredients = new ArrayList<Ingredients>(); otherIngredients.add(new Tofu()); return otherIngredients; } }
これにあわせてSampleClass クラスは下記のように書き換えることができます。
import java.util.*; public class SampleClass{ public static void main(String args[]){ HotPot hotPot = new HotPot(new Pot()); Factory factory = new MizutakiFactory(); hotPot.addSoup(factory.getSoup()); hotPot.addMain(factory.getMain()); hotPot.addVegetables(factory.getVegetables()); hotPot.addOtherIngredients(factory.getOtherIngredients()); } }
水炊きを生成するときには、必ずこの MizutakiFactory クラスを利用するようにすることで、プログラマーによらずいつも同じ水炊きを生成することができるようになります。すなわち、プログラマーが過ちを犯しにくい構造となるわけです。この状態のクラス図を確認しておきます。
この段階ではまだ AbstractFactory パターンとはなっていませんが、「利用するオブジェクトの整合性を保ちたい」という要求には応えることができます。
MizutakiFactory と同じように、SukiyakiFactory、KimuchiFactoryなどを作成することが考えられますので、これらのクラスの親クラスとして Factory クラスを作成しておきます。Factory クラスは、以下のように、getSoup、getMain、getVegetables、getOtherIngredients の4つの抽象メソッドを定義しています。
import java.util.*; public abstract class Factory{ public abstract Soup getSoup(); public abstract Protein getMain(); public abstract List getVegetables(); public abstract List getOtherIngredients(); }
SampleClass クラスの main メソッドでは、引数に与えられた文字列によって、実際に利用する Factory クラスを選択して生成するようにします。
public class SampleClass{ public static void main(String args[]){ HotPot hotPot = new HotPot(new Pot()); Factory factory = createFactory(args[0]); hotPot.addSoup(factory.getSoup()); hotPot.addMain(factory.getMain()); hotPot.addVegetables(factory.getVegetables()); hotPot.addOtherIngredients(factory.getOtherIngredients()); } private static Factory createFactory(String str){ if("キムチ鍋".equals(str)){ return new KimuchiFactory(); }else if("すき焼き".equals(str)){ return new SukiyakiFactory(); }else{ return new MizutakiFactory(); } } }
main メソッドの中では、Factory メソッドの実際の型を知ることなく処理が進んでいます。すなわち、抽象的な Factory クラスを利用して処理を進めていっているのです。このようにすることで、「利用するオブジェクト群をごそっと入れ替える」という要求に応えることができるようになりました。この状態のクラス図を確認してみます。
8.3 AbstractFactory パターンのまとめ
サンプルケースの SampleClass の main メソッドのように、実際にどの Factory サブクラスが利用されるのかは、明確にはされていない状態で、鍋に利用するオブジェクト群を得ています。
AbstractFactory パターンの一般的なクラス図は以下のようになります。
[引用] 『Java言語で学ぶ デザインパターン入門』(結城浩 ソフトバンクパブリッシング株式会社出版 2001年)