Javaによるデザインパターンの5回目です!
教材は、参考文献の「Java言語で学ぶデザインパターン入門第3版」を使わせて頂きます。
今回は、Factory Methodパターンです。とても理解が難しいパターンでした。
それでは、やっていきましょう!
参考文献
参考文献のサンプルプログラムのダウンロード
www.hyuki.com
はじめに
「Javaでデザインパターンを学ぶ」の記事一覧です。良かったら参考にしてください。
Javaでデザインパターンの記事一覧
先に、23種類のデザインパターンを示します。
参考サイト:デザインパターン (ソフトウェア) - Wikipedia
デザインパターン一覧
◆生成に関するパターン
・Abstract Factory:関連するインスタンスを状況に応じて、適切に生成する方法を提供する
・Builder:複合化されたインスタンスの生成過程を隠蔽する。
・Factory Method:生成されるインスタンスに依存しない、インスタンスの生成方法を提供する
・Prototype:同様のインスタンスを生成するために、原型のインスタンスを複製する
・Singleton:あるクラスについて、インスタンスが1つしか存在しないことを保証する
◆構造に関するパターン
・Adapter:元々関連性のない2つのクラスを接続するクラスを作る
・Bridge:クラスと呼び出し側の間の橋渡しをするクラスで、実装を隠蔽する
・Composite:再帰的な構造を表現する
・Decorator:あるインスタンスに対し、動的に付加機能を追加する
・Facade:複数のサブシステムの窓口となる共通のインタフェースを提供する
・Flyweight:多数のインスタンスを共有し、インスタンスの構築のための負荷を減らす
・Proxy:共通のインタフェースを持つインスタンスを内包し、利用者からのアクセスを代理する。Wrapperとも呼ばれる
◆振る舞いに関するパターン
・Chain of Responsibility:イベントの送受信を行う複数のオブジェクトを鎖状につなぎ、それらの間をイベントが渡されていくようにする
・Command:複数の異なる操作について、それぞれに対応するオブジェクトを用意し、オブジェクトを切り替えることで、操作の切り替えを実現する
・Interpreter:構文解析のために、文法規則を反映するクラス構造を作る
・Iterator:複数の要素を内包するオブジェクトのすべての要素に対して、順番にアクセスする方法を提供する
・Mediator:オブジェクト間の相互作用を仲介するオブジェクトを定義し、オブジェクト間の結合度を低くする
・Memento:データ構造に対する一連の操作のそれぞれを記録しておき、以前の状態の復帰または操作の再現が行えるようにする
・Observer:インスタンスの変化を他のインスタンスから監視できるようにする
・State:オブジェクトの状態を変化させることで、処理内容を変えられるようにする
・Strategy:データ構造に対して適用する一連のアルゴリズムをカプセル化し、アルゴリズムの切り替えを容易にする
・Template Method:アルゴリズムは抽象クラスで、処理内容はサブクラスで定義する
・Visitor:データ構造を保持するクラスと、それに対して処理を行うクラスを分離する
今回は、「Factory Methodパターン」をやっていきます!
サンプルコードの理解
Factory Methodパターンは、インスタンスの生成をサブクラスで実施させることで、柔軟にインスタンスを生成できるデザインパターンです。
参考文献の「Java言語で学ぶデザインパターン入門第3版」では、インスタンスの生成方法について、Template Methodパターンを適用したものと紹介されていました。
Template Methodパターンは、処理の枠組みをスーパー多クラスで定義し、具体的な処理はサブクラスに任せるパターンでしたが、Factory Methodパターンは、インスタンスの作り方はスーパークラスで定めて、具体的な作成はサブクラスに任せるパターンであり、サブクラスに任せるという点では、これらのパターンは似ているということですね。
それでは、サンプルコードを見ていくことにします。
今回のPlantUMLのクラス図
PlantUMLで作成したクラス図のコードと画像ファイルです。
@startuml
package framework {
abstract class Factory {
+ final Product create()
# {abstract} Product createProduct()
# {abstract} void registerProduct()
}
abstract class Product {
+ {abstract} void use()
}
Factory -> Product : Creates >
}
package idcard {
class IDCardFactory {
# Product createProduct()
# void registerProduct()
}
Factory <|-- IDCardFactory
class IDCard {
- String owner
+ void use()
+ String toString()
+ String getOwner()
}
Product <|-- IDCard
IDCardFactory -> IDCard : Creates >
}
@enduml
packageが初めて出てきましたね。PlantUMLはpackageを分かりやすく可視化してくれました。
Mainクラス(使う側のクラス)
まずは、Factory Methodパターンどう使われるのかを見るために、Mainクラスを見ていきます。
分かりやすくなると思うので、先に実行結果も示します。
import framework.Factory;
import framework.Product;
import idcard.IDCardFactory;
public class Main {
public static void main(String[] args) {
Factory factory = new IDCardFactory();
Product card1 = factory.create("Hiroshi Yuki");
Product card2 = factory.create("Tomura");
Product card3 = factory.create("Hanako Sato");
card1.use();
card2.use();
card3.use();
}
}
実行結果
Product
(商品、製造物)クラスは、抽象メソッドの use()
(使うメソッド)を持っています。そのサブクラスは、IDCard(身分証明書)クラスです。
Product
クラスのサブクラス(商品、製造物)を作ってくれる抽象クラスが Factory
クラスで、IDカード(身分証明書)を作ってくれるのが、具象クラス(サブクラス)の IDCardFactory
クラスです。
Mainクラスを見ても、まだ、Factory Methodパターンの内容が見えてこないですね。それぞれのソースコードを見ていきます。
Productクラス
Productクラスは抽象クラスで、抽象メソッドの use()
が宣言されています。
package framework;
public abstract class Product {
public abstract void use();
}
IDCardクラス
抽象クラスのProductクラスを継承した、具象クラスのIDCardクラスです。
package idcard;
import framework.Product;
public class IDCard extends Product {
private String owner;
IDCard(String owner) {
System.out.println(owner + "のカードを作ります。");
this.owner = owner;
}
@Override
public void use() {
System.out.println(this + "を使います。");
}
@Override
public String toString() {
return "[IDCard:" + owner + "]";
}
public String getOwner() {
return owner;
}
}
プライベートメンバ変数として、文字列の owner
(所有者名)を持っています。
コンストラクタは、引数で所有者名を受け取り、プライベートメンバ変数に格納しています。
use()
は、IDCardを使用するメソッドで、getOwner()
は、所有者名を返すメソッドです。
toString()
は特殊なメソッドで、クラスのインスタンスを格納している変数が、文字列と結合されるときに、toString()
が呼び出されて、文字列に変換されるメソッドです。use()
で、実際に文字列と結合されています。toString()
は、クラスのインスタンスがprintされるときに、表示を変更したいときに使われるメソッドです。
Factoryクラス
次は、抽象クラスのFactoryクラスです。
package framework;
public abstract class Factory {
public final Product create(String owner) {
Product p = createProduct(owner);
registerProduct(p);
return p;
}
protected abstract Product createProduct(String owner);
protected abstract void registerProduct(Product product);
}
抽象メソッドとして、createProduct()
と registerProduct()
の2つが定義されており、これらは、サブクラスで実装されます。
create()
は、サブクラスで定義される createProduct()
を呼び出し、Productクラスのインスタンスを変数に格納します。その後、サブクラスで定義される registerProduct()
を呼び出し、Productクラスのインスタンスを返します。
このクラスが、Factory Methodパターンの重要なところです。具体的なProductクラスのインスタンス(Productクラスを継承したクラスのインスタンス)の生成を、createProduct()
を抽象メソッドとすることで、サブクラスに任せています。
さらに、registerProduct()
も、生成するProductに適した内容をサブクラスに実装させます。
Template Methodパターンとよく似ています。Template Methodパターンは、create()
で処理手順を定めて、具体的な処理内容(registerProduct()
)はサブクラスに実装させます。ここだけ見ると、Template Methodパターンを使っていると言えると思います。
Template Methodパターンと異なるのは、Template Methodパターンは処理する対象が固定されていました(例えば、ディスプレイに対して出力していた)が、Factory Methodパターンを取り入れることで、処理する対象(生成するインスタンス)をサブクラスに定義させているところです。
例えば、出力対象がディスプレイに固定されていたところを、ファイルに対して出力するオブジェクトに変えることが出来たり、ネットワークに対して出力するオブジェクトに対応することが出来るようになります。
IDCardFactoryクラス
最後に、抽象クラスのFactoryクラスを継承した、具象クラスのIDCardFactoryクラスです。
package idcard;
import framework.Factory;
import framework.Product;
public class IDCardFactory extends Factory {
@Override
protected Product createProduct(String owner) {
return new IDCard(owner);
}
@Override
protected void registerProduct(Product product) {
System.out.println(product + "を登録しました。");
}
}
createProduct()
は、Productクラスを継承したIDCardクラスのインスタンスを生成して返し、registerProduct()
は、Productクラスのインスタンスを受け取り、メッセージを表示します。
Factory Methodパターンのメリットと考察
このサンプルコードでは、IDCard(身分証明書)を作って、それを使って(use()
)いました。Productクラスを継承した別のものに対応することが出来ます。
例えば、クレジットカードや、お店の会員証などに対応する場合は、Productクラスを継承して、それぞれの具象クラスを定義し、それに合わせて、Factoryクラスを継承した具象クラスを定義する感じです。
もし、Factoryクラス(Factory Methodパターン)を使わない場合、ProductクラスとIDCardクラスだけを定義し、Mainクラスは、直接IDCardクラスのコンストラクタを呼ぶ形になると思います。
その場合、Mainクラスは registerProduct()
(登録処理)を実装する必要がありそうです(Productクラスのインスタンスを登録するので、IDCardクラスには実装できない)。
IDCardクラスだけなら問題なさそうですが、クレジットカードや会員証などが増えてきた場合、使う側が複雑化しそうです。Factory Methodパターンを使うと、そのあたりの処理を、Factoryクラスを継承したサブクラスに定義すれば、すっきりしそうです。
おわりに
今回はFactory Methodパターンを学びました。
今回も、Factoryクラスを定義しなかった場合を考えてみて、Factory Methodパターンのメリットを説明したつもりですが、もう少し具体的に説明できる例があれば良かったなと思いました。
今回は以上です!
最後までお読みいただき、ありがとうございました。