第2章 逆引きカタログ ロジック編
Authors:Yoshihara Hidehiko
オブジェクトを利用する側からすれば、使用する際にオブジェクトの詳細を意識したくはありませんよね。
たとえば、条件によってデータファイルの読み込みに使うオブジェクトが異なる場合、CSV形式であればCSVDataReaderオブジェクトを、XML形式であればXMLDataReaderオブジェクトを生成します。
通常はif、else、switchなどの条件分岐を使用して、条件ごとに生成するオブジェクトを変更します。
ここで新たなデータファイル形式への対応が必要になった場合は、新しいオブジェクト生成処理と、条件式を追加しなければいけません。
オブジェクトの使用者は、オブジェクトが使用できる状態で受け渡してもらい、オブジェクトは使うことだけに専念したいものです。
また、このようにオブジェクトの生成処理と使用処理が同じコードに書かれていた場合、オブジェクトの生成処理によってオブジェクトの使用処理が影響を受けてしまい、再利用性が低下してしまいます。
オブジェクトの詳細を隠し、オブジェクトの生成処理と使用処理を分離してくれるのが、ここでご紹介するファクトリパターン/ファクトリメソッドパターンです(
注2)。
ファクトリとは「工場」という意味で、ファクトリパターン/ファクトリメソッドパターンは、オブジェクトを生成するための専用の「工場」を用意し、オブジェクトを生成する処理と生成したオブジェクトを使用する処理を分離させるパターンです。
「工場」であるファクトリクラスでは、オブジェクトの生成のみを行います。
オブジェクト生成の専門家であるファクトリが、オブジェクトの使用者から生成するオブジェクトの種類や生成手順を隠してくれます。
オブジェクトの使用者はファクトリに生成を依頼するだけで、オブジェクトの生成手順や種類を意識する必要はなく、望むオブジェクトを使用できる状態で手に入れることができます。
もし生成するクラスの種類や作成手順が変更されても、ファクトリの中を手直しするだけですみます(図3)(
注3)。
それでは、ファクトリパターンとファクトリメソッドパターンの違いは何でしょうか?
ファクトリパターンは、オブジェクトの生成処理だけでなく、どのオブジェクトを生成するかの判断もオブジェクトェクトの使用者から隠してくれるパターンです。
ファクトリパターンでは、生成するオブジェクトの種類の変更をファクトリの処理の中で動的に行います。
しかし、ファクトリパターンでは、生成するオブジェクトの種類が増えたり、生成処理手順が複雑化した場合、ファクトリ内の処理が冗長で複雑になってしまいます。
そこで登場するのがファクトリメソッドパターンです。
ファクトリメソッドパターンでは、生成するオブジェクトごとにファクトリを用意し、ファクトリに対して共通のスーパークラスを設けることで、オブジェクトの生成処理を柔軟に行います。
スーパークラスでは、オブジェクト生成に共通な処理の実装と、オブジェクトのnewを行うメソッドやオブジェクト固有の生成手順を抽象メソッドとして定義します。
サブクラスでは、抽象メソッドの実装(オブジェクトのnew)を行うだけです。
オブジェクトの生成はサブクラスで行い、サブクラスでは生成処理の差分のみを実装すればよいので、生成処理を簡略化できます(
注4)。
ファクトリメソッドパターンは、1つのファクトリは1つのオブジェクトの生成のみを行うため、生成するオブジェクトの種類の変更を行う場合、ファクトリクラスを切り替える必要があります。
ファクトリメソッドパターンでは多くの場合、オブジェクトの使用者はファクトリのスーパークラスを使用します。
そしてファクトリ指定は、オブジェクト使用者の生成時にコンストラクタで渡したり、ファクトリ設定用メソッドを設けるなどの手段が必要になります。
シンプルなファクトリパターンの使用例は、Strategyパターンの「
具体的な例」で紹介していますので、そちらをご参照ください。
ここではファクトリメソッドパターンの使用例を紹介します。
管楽器を作成してくれる楽器工場があり、この工場で作られる管楽器にはネームプレートの付加サービスが行われています。
工場では管楽器を提供する「管楽器を作成する」を行う前に、「管楽器を加工する」と「ネームプレートを付ける」工程を行います。
ここでは、スーパークラスに共通の処理として「管楽器を作成する」と「ネームプレートを付ける」を実装して、抽象メソッドとして「管楽器を加工する」を定義します。
そして、トランペットを製造するトランペット工場では「管楽器を加工する」にトランペットオブジェクトの生成を行う処理を実装して、サックス工場ではサックスオブジェクトの生成を実装します。
「工場」が分かれることによって、対象オブジェクトの生成だけに専念すればよくなります。
ファクトリメソッドパターンは、このように生成するオブジェクトごとにファクトリを用意することで、ファクトリの処理そのものもシンプルにしてくれます(図4〜5)。
図4 ファクトリメソッドパターンのイメージ
◎AbstractCreator(リスト3)
管楽器オブジェクトを生成するファクトリの抽象クラスです。
オブジェクトの生成処理としてcreate()メソッドを提供しますが、オブジェクトのnewは抽象メソッドとして定義されているcreateInstrument( )メソッドで行います。
生成したオブジェクトを管楽器の抽象クラスWindInstrumentProductとして返します。
リスト3 AbstractCreator.java
public abstract class AbstractCreator {
private String name;
private void mark(WindInstrumentProduct product) {
product.setPlate(name);
}
public void setName(String name) {
this.name = name;
}
public WindInstrumentProduct create() {
WindInstrumentProduct product = createInstrument();
mark(product);
return product;
}
public abstract WindInstrumentProduct createInstrument();
}
◎SaxophoneCreator(リスト4),TrumpetCreater(リスト5)
AbstractCreatorのサブクラスです。
それぞれ、Saxophone、Trumpetをnewする抽象メソッドcreateInstrument()を実装したファクトリクラスです。
リスト4 SaxophoneCreator.java
public class SaxophoneCreator extends AbstractCreator {
public WindInstrumentProduct createInstrument() {
return new Saxophone();
}
}
リスト5 TrumpetCreator.java
public class TrumpetCreator extends AbstractCreator {
public WindInstrumentProduct createInstrument() {
return new Trumpet();
}
}
◎WindInstrumentProduct(リスト6)
管楽器の抽象クラスです。
リスト6 WindInstrumentProduct.java
public abstract class WindInstrumentProduct {
private String plate;
public void setPlate(String plate) {
this.plate = plate;
}
public void printPlate() {
System.out.println("名前:" + plate);
}
public abstract void play();
}
◎Saxophone(リスト7),Trumpet(リスト8)
WindInstrumentProductのサブクラスです。
それぞれ、サックス、トランペットを演奏するplay()メソッドを実装します。
リスト7 Saxophone.java
public class Saxophone extends WindInstrumentProduct {
public void play() {
System.out.println("プププー!!");
}
}
リスト8 Trumpet.java
public class Trumpet extends WindInstrumentProduct {
public void play() {
System.out.println("パパパー!!");
}
}
ファクトリ/ファクトリメソッドパターンは、オブジェクトの生成処理を隠すことで、オブジェクトの使用者とオブジェクト(クラス)の結びつきを低くします。
このことは、再利用性が重要であるオブジェクト指向にとっては利点です。
たとえば、使用したいオブジェクトがまだ作成されていないときも、ファクトリにモックオブジェクトと呼ばれる擬似オブジェクトを生成させることで、オブジェクトの使用者に影響を与えることなく平行開発を行うことができます。
テスト用のモックオブジェクト生成すれば、テストの効率化を高めることができます。
ファクトリ/ファクトリメソッドパターンを使用して変化に強い柔軟なオブジェクト生成を行いましょう。
《注2》
GoFのデザインパターンには、ファクトリメソッドパターン、アブストラクトファクトリパターンの2つが紹介されているだけで、ファクトリパターンは紹介されていません。
しかし実際に用途が多いのは、1つのファクトリが条件によりオブジェクトの生成を変更するシンプルなファクトリパターンですので、ここで解説しておきます。
《注3》
ここで重要なのは、ファクトリがインタフェースか抽象クラスを返すことです。
オブジェクトの使用者はインタフェースまたは抽象クラスに対して処理を行うことで、「何が生成されたか」を意識する必要がなくなりファクトリに対する手直しの影響を受けません。
《注4》
ファクトリメソッドパターンは、名前からご想像できると思いますが、第3章で解説する
テンプレートメソッドパターンと深い関わりがあります。
オブジェクトの生成手順にテンプレートメソッドパターンを用いることによって、オブジェクト生成をフレームワーク化できます。
ただし、必ずしもテンプレートメソッドパターンを使用する必要もありません。