Factory Method
一言でいうと
Factory Method (ファクトリー・メソッド) は、 生成に関するデザインパターンの一つで、 スーパークラスでオブジェクトを作成するためのインターフェースが決まっています。 しかし、 サブクラスでは作成されるオブジェクトの型を変更することができます。
問題
物流管理アプリケーションを作成するとします。 アプリの最初のバージョンは、 トラック輸送のみを処理できます。 コードの大部分は Truck
(トラック) クラス内に存在します。
しばらくして、 このアプリは、 かなり人気が出ます。 海運会社から、 アプリに海上物流を組み込んでほしいという要望が毎日数十件寄せられるようになりました。
素晴らしいことです! でもコードの方はどうしましょう? 現状では、 コードのほとんどは、 Truck
クラスに結合されています。 アプリに Ships
(船) を追加するには、 コードの大部分に手を入れる必要があります。 さらに、 後でアプリに別の種類の輸送手段を追加することにした場合、 おそらくこの変更作業すべてを再度行うことになるでしょう。
結果として、 運輸オブジェクトのクラスに応じてアプリの動作を切り替える条件文だらけの、 かなり厄介なコードができてしまうでしょう。
解決策
Factory Method パターンに従うと、 new
演算子を使用した直接のオブジェクト作成呼び出しを、 特別なファクトリー・メソッドへの呼び出しで置き換えます。 ご心配なく! それでもオブジェクトは、 new
演算子で作成されます。 ただそれはファクトリー・メソッド内で呼び出されます。 ファクトリー・メソッドから返されるオブジェクトはよくプロダクトと呼ばれます。
一見すると、 この変更は無意味に見えるかもしれません。 コンストラクターの呼び出しをプログラムのある部分から別の部分に移動しただけです。 しかし、 これにより、 サブクラスのファクトリー・メソッドを上書きさえすれば作成されるプロダクトのクラスの変更が可能というメリットがあります。
しかし、 わずかな制限があります: これらのプロダクトに共通のベースクラスまたはインターフェースがある場合にのみ、 サブクラスは、 異なる型のプロダクトを返すことができます。 また、 ベースクラス内のファクトリー・メソッドの戻り値の型は、 このインタフェースとして宣言されている必要があります。
たとえば、 deliver
(配送) というメソッドが宣言された Transport
(運送) というインターフェースを、 Truck
と Ship
の両クラスが実装します。 Truck は陸上で、 Ship は海上で貨物を届けるというように、 それぞれのクラスでこのメソッドの実装が異なっています。 RoadLogistics
(陸路物流) クラスのファクトリー・メソッドは Truck オブジェクトを返し、 SeaLogistics
(海上物流) クラスのファクトリー・メソッドは Ship を返します。
ファクトリー・メソッドを使用するコード (クライアントコードとよく呼ばれる) からは、 様々なサブクラスが返す実際のプロダクトの間に違いは見られません。 クライアントはすべての製品を抽象的な Transport
として扱います。 クライアントは、 すべての Transport
オブジェクトが deliver
メソッドを持っていることは知っていますが、 それが厳密にどのように振る舞うかは、 クライアントにとっては重要なことではありません。
構造
-
プロダクト (Product) は、 クリエーターとそのサブクラスによって生成されるすべてのオブジェクトに共通なインターフェースを宣言します。
-
具象プロダクト (Concrete Product) は、 プロダクトのインターフェースの種々の異なる実装です。
-
クリエーター (Creator) クラスは、 新しいプロダクトのオブジェクトを返すファクトリー・メソッドを宣言します。 このメソッドの戻り値の型がプロダクトのインターフェースと一致していることが要点です。
ファクトリー・メソッドを
abstract
と宣言して、 すべてのサブクラスに独自のメソッドの実装を強制することができます。 代わりの方法としては、 基底クラスのファクトリー・メソッドで、 何らかのデフォルトのプロダクト型を返すようにもできます。その名前にもかかわらず、 プロダクトの作成はクリエーターの主要任務ではありません。 通常、 クリエーター・クラスはすでにプロダクトに関連するいくつかの中核となるビジネス・ロジックを持っています。 ファクトリー・メソッドは、 このロジックを具象クラスから分離するのに役立ちます。 比喩を使うとこういうことです: 大規模ソフトウェア開発会社にはプログラマーのための研修部門があるが、 会社全体の主な機能は、 コードを書くことであって、 プログラマーを育成することではない。
-
具象クリエーター (Concrete Creator) は、 異なる型のプロダクトを返すように、 基底クラスのファクトリー・メソッドを上書きします。
ファクトリー・メソッドは、 常に新しいインスタンスを作成する必要はないことに注意してください。 キャッシュ、 オブジェクト・プール、 その他の方法で既存のオブジェクトを返してもかまいません。
擬似コード
この例では、 クライアント・コードを具体的な UI クラスと結合することなく、 プラットフォーム互換な UI 部品を作成するために、 Factory Method を適用する方法を説明します。
基底クラスである Dialog
(ダイアログ) クラスは、 ウィンドウを描画するために異なる UI 部品を使用します。 オペレーティングシステムよって、 これらの UI 部品は少し異なって見えるかもしれませんが、 それでも一貫して動作する必要があります。 Windows 上でのボタンは、 Linux 上でもボタンです。
Factory Method を適用すると、 オペレーティングシステムごとにダイアログのロジックを書き直す必要はありません。 ダイアログの基底クラス内でボタンを生成するファクトリー・メソッドを宣言しておけば、 ファクトリー・メソッドから Windows 形式のボタンを返すようにした、 ダイアログのサブクラスを後で作成できます。 サブクラスは基底クラスからダイアログのコードの大部分を継承しますが、 ファクトリー・メソッドのおかげで、 Windows のボタンをスクリーン上に描画できます。
このパターンが機能するためには、 ダイアログの基底クラスは抽象的ボタン (すべての具象的ボタンが従う基底クラスかインターフェース) と機能する必要があります。 こうしておけば、 どのような種類のボタンであっても、 ダイアログのコードは機能し続けます。
勿論このやり方は、 他の UI 部品にも適用できます。 ただし、 ダイアログに新しいファクトリ・メソッドを追加するたびに、 Abstract Factory (抽象ファクトリー) に近いものとなっていきます。 でも大丈夫です。 このパターンについては後で説明します。
適応性
コードが機能する対象のオブジェクトの正確な型と依存関係が前もってわからない場合に、 Factory Method を使用します。
Factory Method では、 プロダクト作成のコードを実際にプロダクトを使用するコードから分離します。 したがって、 プロダクト作成のコードの拡張が残りのコードから独立して簡単に行えます。
たとえば、 アプリに新しいプロダクトの型を追加するには、 新しい作成クラスのサブクラスを作成し、 その中のファクトリー・メソッドを上書きするだけですみます。
自分の書いたライブラリーやフレームワークのユーザーに内部のコンポーネントを拡張する方法を提供したい場合、 Factory Method を使用します。
ライブラリーやフレームワークのデフォルト動作を拡張する最も簡単な方法は、 おそらく継承です。 しかし、 フレームワークは、 標準コンポーネントの代わりにサブクラスを使用すべきだということをどう認識するのでしょうか?
解決策は、 フレームワーク中に散らばった、 コンポーネント構築コードを削減し、 単一のファクトリー・メソッドに集めることです。 こうすれば、 コンポーネント自体を拡張することに加えて、 誰でもこのメソッドを上書きできます。
それでは、 それがどううまくいくのか見てみましょう。 あるオープン・ソースの UI フレームワークを使ってアプリを書くことを想像してみてください。 アプリには丸いボタンが必要ですが、 フレームワークは四角いボタンしか提供していません。 そこで、 標準の Button
クラスを拡張して、 輝かしい RoundButton
サブクラスを作成します。 しかし、 ここで、 デフォルトのボタンの代わりに新しいボタンのサブクラスを使用するように、 メインの UIFramework
クラスに伝える必要が出てきます。
そのためには、 フレームワークの基底クラスから UIWithRoundButtons
(丸いボタンで UI) というサブクラスを作り、 その createButton
メソッドを上書きします。 このメソッドは基底クラスでは Button
オブジェクトを返しますが、 サブクラスでは RoundButton
オブジェクトを返すようにします。 ここで、 UIFramework
の代わりに UIWithRoundButtons
クラスを使用してください。 それだけです!
毎回再構築する代わりに、 既存オブジェクトを再利用してシステム資源を節約したい場合に、 Factory Method を使用します。
データベース接続、 ファイルシステム、 ネットワーク資源等、 資源を大量に消費するオブジェクトを扱う場合に、 このような状況によく直面します。
オブジェクトの再利用のために何をしなければいけないか考えてみましよう。
- まず、 作成されたすべてのオブジェクトを追跡するための記録場所を作成する必要があります。
- 誰かがオブジェクトを要求してきたら、 プログラムはそのプール (蓄積地) 内の空きオブジェクトを探す必要があります。
- そして、 クライアントのコードに対して見つかったオブジェクトを返します。
- 再使用可能なオブジェクトがない場合、 プログラムは新しいオブジェクトを作成し、 そしてプールに追加する必要があります。
これは結構な量のコードになります! 重複したコードでプログラムを汚染しないように、 このコードは一箇所に納めるべきです。
おそらく、 このコードを置くべき最も明白で便利な場所は、 オブジェクトの再利用をしたいクラスのコンストラクター内です。 しかし、 コンストラクターは定義により常に 新規オブジェクトを返さなければなりません。 既存インスタンスを返すことはできません。
というわけで、 新規オブジェクト作成も既存オブジェクトの再利用もできる通常メソッドが必要となります。 それ、 ファクトリー・メソッドのように聞こえますね。
実装方法
-
すべてのプロダクトが同じインターフェースに従うようにします。 このインターフェースでは、 すべてのプロダクトにとって意味のあるメソッドを宣言する必要があります。
-
クリエーター・クラス内に空のファクトリー・メソッドを追加します。 メソッドの戻り値の型は、 共通のプロダクト・インターフェースと一致する必要があります。
-
クリエーターのコード内で、 プロダクトのコンストラクターの参照を全部探し出します。 プロダクト作成コードをファクトリー・メソッドに抽出しながら、 一つずつ、 コンストラクター呼び出しをファクトリー・メソッドへの呼び出しで置き換えていきます。
ファクトリー・メソッドに、 プロダクトの戻り値の型を決めるためのパラメーターを一時的に追加する必要があるかもしれません。
この時点では、 ファクトリー・メソッドのコードはあまりきれいなものではないかもしれません。 どのプロダクトのクラスをインスタンス化するかを選択するかを決める巨大な
switch
文の塊になっているかもしれません。 しかし、 これはすぐ修正するので、 心配は無用です。 -
次に、 ファクトリー・メソッドに並んでいるプロダクトの型ごとに、 クリエーターのサブクラスを作成します。 基底クラスのファクトリー・メソッドの構築コードの該当部分を抽出して、 それでサブクラスのファクトリー・メソッドを上書きします。
-
プロダクトの型が多すぎて、 すべての型に応じたサブクラスを作成することがあまり現実的ではない場合は、 基底クラスに追加した制御パラメーターを再利用できます。
たとえば、 次のようなクラスの階層があるとします: 基底クラスである
Mail
(郵便) クラスとそのサブクラス、AirMail
(航空便) とGroundMail
(陸送便)、Transport
(運輸) クラスとしてPlane
(飛行機)、Truck
(トラック)、Train
(鉄道)。AirMail
クラスはPlane
オブジェクトのみを使用しますが、GroundMail
はTruck
とTrain
両方のオブジェクトを扱うことができます。 両方のケースを扱うために新しいサブクラス、 たとえばTrainMail
(鉄道便) を作ることもできます。 が、 もう一つ選択肢としては、 クライアント・コードがGroundMail
クラスのファクトリー・メソッドを引数として渡して、 どちらのプロダクトを受け取りたいかを伝えるようにすることです。 -
すべての抽出作業の後、 基底クラスのファクトリー・メソッドが空になった場合は、 そのクラスを抽象クラスとすることができます。 何か残った場合は、 それをメソッドのデフォルト動作とすることができます。
長所と短所
- クリエーターと具象プロダクトとの密な結合を回避。
- 単一責任の原則。 プロダクト作成コードがプログラム中の一箇所にまとめられ、 保守が容易。
- 開放閉鎖の原則。 プロダクトの新しい型をプログラムに導入しても、 既存のクライアント・コードの機能に影響しない。
- 本パターンの適用では、 多数の新規サブクラス導入の必要があり、 コードの複雑化の恐れあり。 既存クリエーター・クラスの階層にこのパターンを適用する場合、 最善の結果が得られる。
他のパターンとの関係
-
多くの設計は、 まず比較的単純でサブクラスによりカスタマイズ可能な、 Factory Method から始まり、 次第に、 もっと柔軟だが複雑な Abstract Factory や Prototype や Builder へと発展していきます。
-
Abstract Factory クラスは、 多くの場合 Factory Methods の集まりですが、 Prototype を使ってメソッドを書くこともできます。
-
Factory Method を Iterator と一緒に使って、 コレクションのサブクラスが、 コレクションと互換な、 異なる型のイテレーターを返すようにできます。
-
Prototype は継承に基づいていないので、 継承の欠点はありません。 一方、 Prototype は、 クローンされたオブジェクトの複雑な初期化が必要となります。 Factory Method は継承に基づいていますが、 初期化のステップは必要ありません。
-
Factory Method は、 Template Method の特別な場合です。 同時に、 Factory Method は、 大きな Template Method の一つのステップとして使うこともできます。
追記
- 多種のファクトリー・パターンとその概念の違いがはっきりしない場合は、 ファクトリー比較を参照してください。