と疑問に思う方もいらっしゃると思いますが、ここでは両者をまとめて解説します。
誤解をおそれずに言うなら、両者にはたいした違いはありません。
この「たいした」の部分のについては、説明を読んでいただければわかるかと思います。
コンテンツ
- Factory ってなに?
- それをやるとなにが嬉しいのか?
- インタフェースで依存度を下げてみる
- 問題はコンストラクタ
- ファクトリを使って依存をなくす
- ファクトリの可能性
- Abstract Factory と Factory Method の違い
- Factory パターンの応用
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 パターンの違いの説明をわかりやすくするために、大幅に改訂しました。
また、ファクトリの応用についても触れてみました。
また、ファクトリの応用についても触れてみました。
"Factory Method パターンとは" の説明に、
返信削除"A クラスと同じくインタフェース C を実装しているので、クラス B はまったく変更することなく、このクラスを扱うことができます。"とありますが、
クラスBにおいて、AFactoryやZFactoryをnewする必要はないのでしょうか?
そのご質問に対する答えは、Yes である場合と No である場合があります。
削除いつ、どのように Factory が利用されるか、またその Factory のライフタイムにどのオブジェクトが責任を持つべきかによって決まります。
以下、これについて詳しくご説明しますが、だいぶ長文になってます。
もしよろしければ、お付き合いください。
例えば、統一された UI で複数の SNS を操作するためのクライアントアプリケーションを作りたいとします。
手はじめとして、Facebook と Twitter をサポートしてみましょう。
ログイン、フィードの取得と表示、投稿など全ての場面で条件分岐していたら、あっという間に新しい SNS をサポートするのが大変な作業になるのは、想像に難くありません。
ここで、Factory Method パターンの適用を検討してみましょう。
ログインやフィードの取得と内容のレンダリング、そして投稿などの機能はどの SNS にも共通して必要ですが、実装の内容はまちま
ちです。
そこで、ビジネスロジックが操作の対象がどの SNS なのかをいちいち意識しなくてもいいように、共通のインタフェースを用意しま
す。
各 SNS とやりとりするオブジェクトは、そのインタフェースを実装します。
さらにビジネスロジックの各 SNS への依存を無くすために、Factory パターンを適用してみます。
追加するオブジェクトは、以下のとおりです。
- SNSContextFactory (抽象クラス、またはインタフェース)
- FacebookObjectsFactory (SNSContextFactory を継承する Facebook 用オブジェクト群のファクトリ)
- TwitterObjectsFactory (SNSContextFactory を継承する Twitter 用オブジェクト群のファクトリ)
FacebookFactory は Facebook を操作するオブジェクト、TwitterFactory は Twitter を操作するオブジェクトを生成します。
おそらく、それらのオブジェクトもまた共通のインタフェースをそなえていて、ビジネスロジックからはそれを通して同じように操作できるようになっているはずです。
FacebookFactory と TwitterFactory は、共通するスーパークラス (またはインタフェース) SNSContextFactory を継承しています。
SNSContextFactory の名前が示すとおり、これらは一連の処理のコンテキトを形成するものです。
つまり、同じ "投稿" という操作でも処理の対象が Facobook なら Facebook に対する近況の投稿になり、Twitter なら ツイートになるという意味で、これらのファクトリが生成するオブジェクト群はコンテキストなのです。
さて、ここでご質問の "AFactoryやZFactoryをnewする" ことについてのハナシに関係してくるのですが、今回の場合 Factory はいつインスタンス化されるべきでしょうか ?
それは、どの SNS を処理対象とするかが決まるときです。
SNS の選択のしかたは、いくつか考えられます。
例えば
- ログイン画面に各 SNS のログインボタンを表示し、ユーザがいずれかのボタンをクリックした時点で対象とする SNS が決まる。
- フィードには全ての SNS から取得した内容をマージしたものを表示し、投稿の時点で、どれに投稿するかをユーザが選択する。
などです。
他にもフィードを表示する SNS や 投稿先をあらかじめ設定できるとか、いろいろやりかたはあるでしょう。
どこかの時点で対象となる SNS が決まるのなら、そのタイミングで Factory を生成すればいいでしょう。
ログインボタンのクリックで決まるのであれば、そのイベントから直接コールされるビジネスロジックで生成してもいいと思います。
つまり、この場合の質問への答えは Yes になります。
この方法では依存性はまだ僅かに残されることになりますが、同時に導入される複雑さに見合う効果が見込まれないのであれば、これ以上の依存性の排除にこだわる必要はありません。
または、常にすべての SNS を操作しなければならない場合、クライアントアプリ の起動時に全ての SNS の Factory をインスタンス化すべきかもしれません。
この場合は、たいていクライアントアプリ自体のライフタイムと Factory のライフタイムがほぼ一致することになるでしょう。
ですので、起動コードか初期化コードが Factory をインスタンス化し、そのライフタイムに責任を持ちます。
この場合、起動コードや初期化コードがビジネスロジックから分離する必要がないほど充分に小規模なアプリケーションなら質問への答えは Yes かもしれませんし、そうでなければ No かもしれません。
ユーザがいつでも好きなように SNS を選択できるようにするのであれば、その選択状態を常に管理しておけるようにコンテキストマネージャーのようなオブジェクトを用意して、それに Factory のライフタイムを管理する責任を任せるべきかもしれません。
この場合、実際に Factory のインスタンスを生成する処理はコンテキストマネージャ内に隠蔽され、ビジネスロジックからは完全に隔離されます。
つまり、質問への答えは No になります。
このように、アプリケーションの設計はいつもケースバイケースです。
Factory パターンは主に依存性の排除のために利用されます。
ではなんのために依存性を排除するのかというと、複雑さの抑制とモジュール性の確保のためです。
しかし、案外依存性の排除は複雑さを増大させる傾向があるものです。
ですので、これ以上小分けにする必要がないところで依存性排除のためだけのコードを追加すると、ほとんど恩恵はないのに複雑さだけが増えることにもなりかねません。
このあたりは、バランスが大事だと思います。