Factory Methodパターン


 ★★★

Factory Methodパターン

インターフェースだけを規定、インスタンスの生成はサブクラス

Factory Method パターンは、インスタンスの生成方法に一工夫加えることで、より柔軟にインスタンスを生成することを目的とするものです。

Factory Method パターンでは、インスタンスの生成をサブクラスに行わせることで、より柔軟に生成するインスタンスを選択することが可能となります。

Template Method パターンでは、スーパークラス側でフレームワークを作り、サブクラス側で具体的な処理の実装を行いました。このパターンをインスタンス生成の場面に適用したものが、Factory Method パターンです。Factory Method パターンでは、インスタンスの作り方をスーパークラス側で定めますが、具体的なクラス名までは定めません。具体的なクラス名は全てサブクラス側で行います。これによってインスタンス生成のためのフレームワークと、実際のインスタンス生成のクラスとを分けて考えることができるようになります。

1. 利用するアルゴリズムを変更できるようにする


DBのデータに対しある演算をし、その結果を表示するようなクラスで、どのような演算かを利用時に決めたいとき、その演算のアルゴリズムを記述したメソッドをサブクラス側に実装しておきます。

例えば、集計してその結果を表示するようなクラスの場合、部とか課とかの特定の項目で集計するクラスのインスタンスを生成するメソッドをサブクラス側に記述しておきます。スーパークラスはデータを得た後、サブクラスのメソッドを呼び、その集計結果を受け取るということになります。

こうしておけば、いろいろな項目で集計する必要があっても、部で集計するサブクラス、課で集計するサブクラスというようにサブクラスを追加していくだけですみます。

このとき、スーパークラスのメソッドはどう記述すればよいでしょうか。以下の3通りが考えられます。

1. 抽象メソッドにする。
サブクラスに実装しなければコンパイルエラーとなる。
2. デフォルト動作を実装しておく。
サブクラスに実装しなければスーパークラスで実装された動作をする。
3. 例外を発生させる。
サブクラスに実装しなければ実行時に例外が発生する。

もし、Factory Method パターンを使用しなければ、通常は if や switch などを使用して、集計する項目ごとに分岐させることになります。そして、ここで新たな項目での集計が必要になった場合は、条件式を追加しなければならなくなります。

2. 必ず一緒に利用しなければならないクラスを指定する

あるクラスと必ず一緒に利用しなければならないクラスがある場合、そのクラスのインスタンスを生成したときに自動的にもう一方のクラスのインスタンスも生成されるようにする。

例えば、もう一方のクラスのインスタンスが自動的に生成されないとしたら、これらのクラスを対として使う必要があることをプログラマーが意識してコーディングをしないといけないことになります。

プログラマーがそこまで意識してクラスを使ってくれれば良いですが、気付かなかった場合は間違ったコードがプログラムに紛れ込むことになります。これを避けるための工夫が必要になります。


Factory Method パターンは、Template Methodパターンに似ていますが、こちらはそのメソッドがインスタンス生成のみを行い、生成に重点が置かれています。



例題

区分ごとの合計値を計算するクラスを作りなさい。また、区分名の取得は、既存のクラスがあるのでそれを利用するようにしなさい。


実行結果
区分1 800  
区分2 600  








既存クラス

Data.java
public class Data { protected int no; protected int value; protected 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; } }
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]; } }

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

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 Data[] calculateTotal(); public void operation() { total = calculateTotal(); } }
SubTotal.java
public class SubTotal extends AbstractTotal { 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; } public int getDivision(int n) { return total[n].no; // 区分 } public int getTotal(int n) { return total[n].value; // 合計値 } }
Main.java
public class Main { public static void main(String[] args) { SubTotal sub = new SubTotal(); SubDivisionName div = new SubDivisionName(); sub.operation(); // 区分ごとの合計値算 System.out.println(div.getName(sub.getDivision(0)) // 区分名 + " " + sub.getTotal(0)); // 合計値 System.out.println(div.getName(sub.getDivision(1)) // 区分名 + " " + sub.getTotal(1)); // 合計値 } }

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

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(); } }
Main.java
public class Main { public static void main(String[] args) { SubTotal sub = new SubTotal(); AbstractDivisionName div = sub.createDivisionName(); sub.operation(); // 区分ごとの合計値算 System.out.println(div.getName(sub.getDivision(0)) // 区分名 + " " + sub.getTotal(0)); // 合計値 System.out.println(div.getName(sub.getDivision(1)) // 区分名 + " " + sub.getTotal(1)); // 合計値 } }

Template Method パターンで作成しました。区分ごとの合計値を求める、同じ処理をする既存クラス(SubTotalSummary クラス)があっても、利用していません。ソースをコピーして貼り付けているわけです。

そうすると、元のクラスにバグがあったりすると、コピーしたすべてのクラスで修正しなければならなくなるわけです。すべてのクラスが見つかればいいですが、見つからず修正されないまま放置されることになります。


また、SubTotal クラスと SubDivisionName クラスは必ず一緒に利用しなければならないとなっています。つまり、SubTotal クラスを利用して合計値を計算し、SubDivisionName クラスを利用して区分の名称を取り出しています。

こういった、一緒に使わなければならないクラスの組合せが多かったり、一緒に使うクラスの数が何十もあったのではとても覚え切れません。

スーパークラス AbstractTotal の operation では、処理の枠組みが記述されています。そして、サブクラス SubTotal では、処理の内容が書かれています。前に説明しました Template Method パターンでも、スーパークラス側で処理の枠組みを作り,サブクラス側で具体的な処理の実装を行いました。つまり、Factory Method パターンは Template Method パターンと同じです。Template Method パターンをインスタンス生成の場面に適用したものが、Factory Method パターンというわけです。

Factory Method パターンでは、インスタンスの作り方をスーパークラス側で定めるが,具体的なクラス名までは定めません。具体的な肉付け(ここでは、既存クラスの SubTotalSummary クラスを利用するということ)は全て SubTotal というサブクラス側で行います。これによってインスタンス生成のための枠組みと、実際のインスタンス生成のクラスとを分けて考えることができます。

したがって、合計値を求める別の方法があったとしても、別の方法を行うクラスのインスタンスを生成するサブクラスを追加するだけで済みます。これが、1の「利用するアルゴリズムを変更できる」ということです。


つぎに、区分の名称を得るために既存のクラス SubDivisionName を利用しています。こういったクラスの数が少なければ、どのクラスを利用すればよいか迷わないかもしれませんが、たくさんあった場合には間違ったクラスのインスタンスを生成してしまうこともあるかもしれません。そこで、使用するクラスのインスタンスを生成するメソッド(createDivisionName メソッド)をあらかじめスーパークラス(あるいはサブクラス)の中に記述しておけば、利用者がクラスの選択で迷うことはなくなります。これが、2の「一緒に利用しなければならないクラスを指定する」ということです。