Factory パターンって、Abstract Factory パターンのことを言ってるの?それとも Factory Method パターンのこと?
と疑問に思う方もいらっしゃると思いますが、ここでは両者をまとめて解説します。
誤解をおそれずに言うなら、両者には
たいした違いはありません。
この「たいした」の部分のについては、説明を読んでいただければわかるかと思います。
コンテンツ
Factory ってなに?
直訳すれば工場ですよね。
まぁ、実際に工場なんですけど、何の工場か?
もちろん、オブジェクト指向のハナシをしてるんですから、決まってますよね。
オブジェクトです。
オブジェクトのインスタンスを、生成する工場なわけです。
a クラスのインスタンスを生成するための a ファクトリとか、x クラスのインスタンスを生成する x ファクトリとかなわけです。
それをやるとなにが嬉しいのか?
ファクトリを使用することで、オブジェクトのインスタンスを作る箇所での、依存度を下げることができるのです。
具体的に見てみましょう。
以下では、Java 風の擬似コードを使って説明していきます。
class A {
// なにかの処理
}
class B {
method(){
A a = new A();
}
}
この擬似コードでは、クラス B はクラス A に依存しています。
"A" というクラスを、そのまま変数の型に使用しているのと、A クラスのコンストラクタに直接アクセスしているからです。
なので、もし同じインタフェースを実装している別のクラスや、A クラスのサブクラスを作っても、クラス B の "method" メソッドを変更しないと、それに対応することはできません。
インタフェースで依存度を下げてみる
とりあえず、依存度を下げるための一歩として、インタフェースを用意しましょう。
interface C {
// いくつかのメソッド
}
class A implements C {
// インタフェースCの実装
}
クラスBは、以下のようになるでしょう。
class B {
method(){
C c = new A();
}
}
しかし、これだけでは、依存度はたいして下がっていません。
問題なのは、 "new A();" の部分です。
根本的な問題は、コンストラクタへのアクセスにあるのです。
問題はコンストラクタ
コンストラクタの呼び出しを、コードのいろんなところに散りばめることは、コード全体に接着剤をぶちまけるようなものです。
アプリケーション全体が強力な依存性で接着されて、岩盤のように固く、変更困難になってしまいます。
"new A();" というコードがクラス B に書いてある限り、インタフェース C は依存度を下げる役には立ちません。
コンストラクタの呼び出しを、排除する必要があります。
でも、どこかしらでクラスを明示してコンストラクタを使用しなければ、オブジェクトを生成することはできませんね。
リフレクションでも使えば別ですが、それはそれで、多用するのは問題があります。
ファクトリを使って依存をなくす
コンストラクタの呼び出しが避けられないのなら、いっそ一箇所にまとめて隔離しましょう。
それがファクトリってわけです。
ファクトリを使うと、たとえばクラス B はこうなります。
class B {
method(){
C c = CFactory.newInstance();
}
}
クラス A は、インタフェース C を実装しているとします。
CFactory は、newInstance() メソッド内で、クラス A のインスタンスを生成して返します。
クラス B からはクラス A のコンストラクタ呼び出しは排除され、CFactory の newInstance メソッド内に隠蔽されました。
これで、クラス B はインタフェース C を実装しているものならなんでも、変更なしに扱うことができます。
このクラスには、もはやクラスA の存在を匂わせるものは一切ありません。
実際、これが擬似コードでなく実装コードだったとしたら、この時点でクラス A のインポートステートメントも削除することができ、A というクラス名はもはやクラス B のソースから一切姿を消してしまうはずです。
このように、ファクトリを使用すれば単に依存度を下げることができるだけでなく、依存をまったくのゼロにすることができます。
ファクトリの可能性
上記のコードで出てきた CFactory は、単にコンストラクタを隠蔽しているだけでなく、実は大きな可能性を秘めています。
newInstance メソッドは、実際にクラス A を返すこともできますし、クラス A のサブクラスを返すこともできます。
あるいは、インタフェース C をインプリメントしているものの、クラス A とはまったく関係がないクラスを返すこともできます。
どのクラスを返すかは、クラス B とは関係のない、別のところで決めることができるのです。
クラス B を作ったときには想定していなかったクラスを、あとから追加して newInstance メソッドで返すようにすることができます。
これぞまさに
自由!
実装上の自由というのは、依存性がゼロになったときにだけ生まれるものです。
インスタンス生成に自由をもたらすことが、ファクトリの目的です。
Abstract Factory と Factory Method の違い
この 2 つは、いずれも言わずと知れた
デザインパターンの代表格です。
ですが、これらの違いのわかりにくさが、なんとなくファクトリ全体をとっつきにくいものにしているような気がします。
名前が似ているだけでなく、同じ目的を達成するためのテクニックセットの、それぞれ交換可能な 2 つの部品というような関係です。
なので、その境界がどこにあるのかについて、混乱しやすいのではないでしょうか。
でも、わかってしまえば結構簡単なものです。
Abstract Factory パターンとは
ここまでで書いてきた擬似コードの全体像を見てみると、こんなかんじですね。
interface C{}
class A implement C {}
class CFactory {
C newInstance(){
return new A():
}
}
class B {
method(){
C = CFactory.newInstance();
}
}
クラス B は、クラス CFactory にクラス A のインスタンスの生成を委譲しています。
実は、これは Abstract Factory パターンそのものです。
ファクトリの
クライアントとなるオブジェクトが、ファクトリオブジェクトにインスタンスの生成を委譲するという関係が、Abstract Factory パターンです。
Factory Method パターンとは
ファクトリは、オブジェクト生成に自由をもたらすとは、すでに言いました。
では、実際に生成されるオブジェクトが切り替わるように変更してみましょう。
interface C{}
class A implement C {}
class Z implement C {}
abstract class CFactory{
abstract C newInstance();
}
class AFactory extends CFactory{
C newInstance(){
return new A():
}
}
class ZFactory extends CFactory{
C newInstance(){
return new Z():
}
}
class B {
method(){
C = CFactory.newInstance();
}
}
新しく生成したいクラスは、4 行目で追加した Z です。
A クラスと同じくインタフェース C を実装しているので、クラス B はまったく変更することなく、このクラスを扱うことができます。
生成するインスタンスを切り替えるためには、if 文などの条件分岐を使用することもできますが、条件分岐が増えることはコードの複雑性の増大に直結してしまいます。
なので、ここではサブクラスによって切り替えることにします。
そのために、クラス CFactory を抽象クラスにして、サブクラス AFactory と ZFactory を作りました。
AFactory はクラス A のインスタンスを生成し、ZFactory はクラス Z のインスタンスを生成します。
今回は CFactory クラスにはじめて階層構造をもたせたので、変更範囲が広いですが、次回からは CFactory クラスのサブクラスを作るだけで対応できるようになったことが、おわかりいただけるでしょうか。
このように、
親クラスであるファクトリが、実際のオブジェクトの生成をサブクラスに委譲するのが、Factory Method パターンです。
これは、
Template Method パターンの一つの例でもあります。
これによって、CFactory からもクラス A への依存が取り除かれました。
クラス A に依存するのは AFactory だけ、クラス Z に依存するのは ZFactory だけ、というように各インスタンスに対応するファクトリサブクラスの中に、依存性が閉じ込められたわけです。
さて、ここで再び問います。
これは
なにが嬉しいんでしょうか?
たしかに依存関係は最小限に圧縮されたわけですが、シンプルに
class B {
method(){
if (/* なにかの条件 */) C = new A();
else C = new Z();
}
}
と書けばわずか 2 行で済むのに比べると、ものすごい量です。
でも、ちょっと待って下さい。
これは単なる擬似コードなので、できるかぎり見やすいように、シンプルに書いたものです。
たしかに、このような
極シンプルなケースであれば、ファクトリよりただの条件分岐のほうがベターな場合があります。
しかし、実際のアプリケーションのコードでは、こうはならない場合がほとんどでしょう。
Factory パターンの応用
例えば、プログラミング向けテキストエディタの、シンタックスハイライティングを実装するとします。
キーワードやリテラル、コードブロック、コメントなどシンタックスの種類はたくさんありますが、どれもプログラミング言語ごとに内容は違います。
このたくさんのシンタックスを、それぞれオブジェクトであらわすとしたらどうでしょうか。(実際にそれが最適解かどうかは別としてです)
プログラミング言語というのは、移り変わりが激しいものです。
日々生まれては消えていくし、昨日は誰も知らなかった言語が今日はメジャーになっているかもしれません。
もし、条件分岐でいちいちオブジェクトを切り替えていたとしたら、アプリケーション全体に渡って同じような条件分岐が散りばめられることになります。
条件分岐を採用してしまったら、新しいプログラミング言語に対応するのは、非常にコストがかかる仕事になります。
こういう場合、全てのシンタックスオブジェクトの生成を
一手に引き受けるファクトリを用意するというのも、効果的な手段です。
対応するプログラミング言語 1 つに対して、ファクトリのサブクラスを 1 つ (または 1 セット) 作ります。
こうすることで、ファイルタイプやユーザの選択によってファクトリが切り替わると、ハイライティングルールが一挙に切り替わるようにできます。
新しい言語に対応する場合に必要な作業は、新しいファクトリを 1 つ作ることと、新しいファイルタイプにそれを関連付けること、そして選択オプションを追加することだけです。
それ以外の既存のオブジェクトについては、全く心配する必要がありません。
他にも、例えばユーザのロールによって、表示の内容やアクセスできる機能のセットを切り替えるようないわゆる権限の実装や、ルック & フィールの切り替えなどでも、ファクトリを利用することができます。
また、OS やミドルウェア、フレームワークの種類によってアイソレーションレイヤ全体を切り替えるようなファクトリを用意することで、ポータビリティの大幅な向上を実現するというような、大規模な仕組みにも応用することができます。
2013.02.13
Abstract Factory パターンと Factory Method パターンの違いの説明をわかりやすくするために、大幅に改訂しました。
また、ファクトリの応用についても触れてみました。