Abstract Factoryパターン


 ★☆☆

Abstract Factoryパターン

インスタンスの生成を専門に行う抽象クラスを用意する

Abstract Factory パターンとは、インスタンスの生成を専門に行う抽象クラスを用意することで、整合性を必要とされる一連のオブジェクト群を間違いなく生成するパターンです。



例えば、街の中でタクシーに乗ることを考えて見ましょう。まず、屋根に何かが乗っかっている自動車を探しますね。そして、それが近づいてきてタクシーと確認できたら、空車であることを確認して手を挙げますね。これは、A社のタクシーだからこうして、B社だからこうして、というように方法を変えることはありません。どの会社のタクシーでも同じ手順で行います。

つまり、 A社だからB社だからではなく、タクシーという抽象概念に対して一連の手順を行っているわけです。


データベースを使用する例を挙げてみましょう。



たとえば、DBMSであるSQL ServerとOracleをシステムによって切り替えられるようにすることを考えて見ましょう。

ひとつには、まったく同じメンバを持つ同名のクラスを2種類用意しておくという案があります。SQL Serverを使用するクラスもOracleを使用するクラスも、どちらも同じDataBaseという名前のクラスにして、まったく同じメンバを持たせるのです。ソースファイルを交換するだけでDBMSを変えられ、プログラムの内容には一切変更が不要です。ただし、見た目だけでは区別しにくいため、取り扱いを誤ってしまう危険性があります。



それなら、メンバは同じでクラス名だけを変えるという案(Template Methodパターン)はどうでしょう。SQL Server用はSqlDataBaseクラス、Oracle用はOracleDataBaseクラスとするのです。取り扱いを誤ることはないでしょうが、DBMSを変更する際に、プログラムの中にあるクラス名(new をするクラス)を変更しなければなりません。いずれも問題があるのです。


Abstract Factory パターンでも、インスタンス生成のところは Factory Method パターンと同じです。ただ Factory Method と違い、Factory クラスは生成のみを専門に受け持ち、自身では状態を持ちません。Factory Method は new する代わりに生成メソッドを追加することであり、Abstract Factory は抽象的な存在(=自身の実体を持たない)を意味しています。

このような生成を専門につかさどるメソッドがあるのは、複数のインスタンスの組を生成するためです。単一のクラスのインスタンスを生成するならそのクラスに create メソッドを作るか、あるいは単に new を行えばいいだけです。Factory Method のように生成メソッドを持っていることが重要なのではなく、生成するインスタンスの組を持っていることがポイントなのです。

そしてこのような Factory クラスを複数作って、状況に応じて生成されるインスタンスの組を切り替えます。

知らないからこそ、入れ替えができるのです。入れ替えられるからこそ、部品としての価値が高くなるのです。この「交換可能性」については、クラスを設計するときに常に意識しておくことが必要になります。



例題

区分ごとの合計値を計算するクラスと、すべての合計値を計算するクラスとを作り、実行時にどちらを使用するかを決められるようにしなさい。


実行結果
引数 sub
区分1 800

    引数 whole
総合計 1400






共通クラス

Data.java
public class Data { protected int no; protected int value; public Data(int no, int value) { this.no = no; this.value = value; } }
AbstractSummary.java
public abstract class AbstractSummary { protected Data[] list; public AbstractSummary() {} public AbstractSummary(Data[] list) { this.list = list; } public abstract Data[] calculateTotal(); }
SubTotalSummary.java
public class SubTotalSummary extends AbstractSummary { public SubTotalSummary() {} public SubTotalSummary(Data[] list) { super(list); } public Data[] calculateTotal() { int no = list[0].no, n = 0; Data[] total = new Data[list.length]; total[0] = new Data(no, 0); for (int i = 0; i < list.length; i++) { if (list[i].no != no) { no = list[i].no; total[++n] = new Data(no, 0); } total[n].value += list[i].value; } return total; } }
WholeTotalSummary.java
public class WholeTotalSummary extends AbstractSummary { public WholeTotalSummary() {} public WholeTotalSummary(Data[] list) { super(list); } public Data[] calculateTotal() { Data[] total = new Data[1]; total[0] = new Data(0, 0); for (int i = 0; i < list.length; i++) { total[0].value += list[i].value; } return total; } }
AbstractDivisionName.java
public abstract class AbstractDivisionName { public abstract String getName(int n); }
SubDivisionName.java
public class SubDivisionName extends AbstractDivisionName { // 実際にはデータベースなどから名称を得る。 private String[] name = {"", " 区分1 ", " 区分2 "}; public String getName(int n) { return name[n]; } }
WholeDivisionName.java
public class WholeDivisionName extends AbstractDivisionName { public String getName(int n) { return "総合計"; } }

Abstract Factoryパターンを使用しない例

AbstractTotal.java
public abstract class AbstractTotal { // 区分 , 数値 (実際には DB などから数値を得る。) protected Data[] list = new Data[] { new Data(1, 100), new Data(1, 500), new Data(1, 200), new Data(2, 400), new Data(2, 200) }; protected Data[] total; // 合計値 public abstract int getDivision(int n); public abstract int getTotal(int n); public abstract AbstractSummary getSummary(); public abstract AbstractDivisionName createDivisionName(); public void operation() { AbstractSummary sum = getSummary(); total = sum.calculateTotal(); } }
SubTotal.java
public class SubTotal extends AbstractTotal { public AbstractSummary getSummary() { return new SubTotalSummary(list); } public int getDivision(int n) { return total[n].no; // 区分 } public int getTotal(int n) { return total[n].value; // 合計値 } public AbstractDivisionName createDivisionName() { return new SubDivisionName(); } }
WholeTotal.java
public class WholeTotal extends AbstractTotal { public WholeTotalSummary getSummary() { return new WholeTotalSummary(list); } public int getDivision(int n) { return total[0].no; // 区分 } public int getTotal(int n) { return total[0].value; // 合計値 } public AbstractDivisionName createDivisionName() { return new WholeDivisionName(); } }
Main.java
public class Main { public static void main(String[] args) { AbstractTotal total = null; if (args[0].equals("sub")) total = new SubTotal(); else total = new WholeTotal(); total.operation(); AbstractDivisionName divisionName = total.createDivisionName(); System.out.println(divisionName.getName(total.getDivision(0)) + " " + total.getTotal(0)); } }

Abstract Factoryパターンを使用した例

AbstractTotal.java
public abstract class AbstractTotal { // 区分 , 数値 (実際には DB などから数値を得る。) protected Data[] list = new Data[] { new Data(1, 100), new Data(1, 500), new Data(1, 200), new Data(2, 400), new Data(2, 200) }; protected Data[] total; // 合計値 public abstract int getDivision(int n); public abstract int getTotal(int n); public abstract AbstractSummary getSummary(); public void operation() { AbstractSummary sum = getSummary(); total = sum.calculateTotal(); } }
SubTotal.java
public class SubTotal extends AbstractTotal { public AbstractSummary getSummary() { return new SubTotalSummary(list); } public int getDivision(int n) { return total[n].no; // 区分 } public int getTotal(int n) { return total[n].value; // 合計値 } }
WholeTotal.java
public class WholeTotal extends AbstractTotal { public AbstractSummary getSummary() { return new WholeTotalSummary(list); } public int getDivision(int n) { return total[0].no; // 区分 } public int getTotal(int n) { return total[0].value; // 合計値 } }
AbstractFactory.java
public abstract class AbstractFactory { public abstract AbstractTotal getTotal(); public abstract AbstractDivisionName getDivisionName(); }
SubFactory.java
public class SubFactory extends AbstractFactory { public AbstractTotal getTotal() { return new SubTotal(); } public AbstractDivisionName getDivisionName() { return new SubDivisionName(); } }
WholeFactory.java
public class WholeFactory extends AbstractFactory { public AbstractTotal getTotal() { return new WholeTotal(); } public AbstractDivisionName getDivisionName() { return new WholeDivisionName(); } }
Factory.java
public class Factory { private AbstractFactory factory; public Factory() {} public Factory(String c) { if ("sub".equals(c)) { factory = new SubFactory(); } else { factory = new WholeFactory(); } } public AbstractFactory getFactory() { return factory; } }
Main.java
public class Main { public static void main(String[] args) { AbstractFactory factory = new Factory(args[0]).getFactory(); AbstractTotal total = factory.getTotal(); total.operation(); AbstractDivisionName divisionName = factory.getDivisionName(); System.out.println(divisionName.getName(total.getDivision(0)) + " " + total.getTotal(0)); } }

Factory Method パターンで記述しています。

SubTotal クラスと WholeTotal クラスを利用しますが、そのクラス名を利用する側(main メソッド)で記述しています。この例では実行時に指定されたパラメータによって利用するクラス名を決定しています。もし、新たに別のクラスが追加されていくような場合、ユーザのソースを変更しなければなりません。

SubTotal クラスか、WholeTotal クラスか、どちらかのインスタンスを Factory というクラスのコンストラクタで生成しています(なお、この例では、SubDivisionName クラスか、WholeDivisionName クラスかも Factory クラスで生成しています)。もし、新たに別のクラスが追加されていくような場合でも、クラスの追加とともに Factory クラスを書き換えてあげればいいので、ユーザのソースを変更する必要はありません。

Factory Method パターンと Abstract Factory パターンとは似ていますが、これらパターンの違いは、この Factory クラスです。

main メソッドの中では、getFactory メソッドの戻り値の実際の型を知ることなく処理が進んでいます。すなわち、抽象的な factory インスタンスを利用して処理を進めていっているのです。このようにすることで、利用するオブジェクト群を入れ替えるということができます。