Abstract Factory
一言でいうと
Abstract Factory (抽象ファクトリー、 抽象工場) は、 生成に関するデザインパターンの一つで、 関連したオブジェクトの集りを、 具象クラスを指定することなく生成することを可能とします。
問題
家具工場シュミレーターを作ることを想像してみてください。 コードは以下ようなものを表現するクラスで構成されています:
-
関連する製品ファミリー、 たとえば:
Chair
(椅子) +Sofa
(ソファー) +CoffeeTable
(コーヒーテーブル)。 -
このファミリーの様式。 たとえば、
Chair
+Sofa
+CoffeeTable
には以下のような様式のものがあります:Modern
(現代風)、Victorian
(ビクトリア様式)、ArtDeco
(アールデコ様式)。
様式の一致する家具を製造するには、 同じ様式の家具オブジェクトを生成する何らかの方法が必要となります。 様式の異なる家具が配達されたら、 客は怒りますよね。
プログラムに新しい製品や製品群を追加する時、 既存コードの変更はしたくありません。 家具メーカーはカタログを常に更新しますが、 そのたびに中核のコードを変更するのは避けたいところです。
解決策
Abstract Factory パターンでは、 まず、 家具ファミリーの個別製品 (椅子、 ソファー、 コーヒーテーブル) ごとに、 明示的にインターフェースを宣言します。 様式別の製品は、 これらのインターフェースに従って作成します。 たとえば、 椅子の異なる様式に対応する変種 (バリエーション) を作るには、 Chair
インターフェースの実装を行い、 コーヒーテーブルの変種を作るには、 CoffeeTable
インターフェースを実装する、 といった具合です。
次に 抽象ファクトリー を宣言します。 これは、 製品ファミリー全製品の生成メソッド (例: createChair
、 createSofa
、 createCoffeeTable
) を並べたインターフェースとなります。 これらのメソッドは、 このように抽出したChair
、 Sofa
、 CoffeeTable
といったインターフェースで表現された、 製品の 抽象 型を返す必要があります。
では製品の様式ごとの変種はどうするか? というと、 製品の様式ごとに、 AbstractFactory
を基に別々のファクトリー・クラスを作成します。 ファクトリーは、 特定の種類の製品を返すクラスです。 たとえば、 ModernFurnitureFactory
(現代風家具ファクトリー) は、 ModernChair
(現代風の椅子)、 ModernSofa
(現代風のソファー)、 そして ModernCoffeeTable
(現代風のコーヒーテーブル) のオブジェクトのみ作ることができます。
クライアント側のコードは、 ファクトリー、 製品ともそれぞれの抽象インターフェースを通して機能します。 これにより、 クライアントのコードに渡すファクトリーの型を変更したり、 クライアントの受け取る製品の様式を変更しても、 クライアント側コードは問題なく動作します。
クライアントはファクトリーに椅子を生成してほしいとします。 クライアントは、 ファクトリーのクラスを気にする必要もなく、 どういう種類の椅子が返ってくるかを気にする必要もありません。 現代風の椅子でも、 ビクトリア様式の椅子でも、 Chair
という抽象インターフェースを使って、 同じように扱います。 こういうやり方に従うと、 クライアントが知っておく必要があることは、 椅子が何らかの方法で sitOn
(腰掛ける) メソッドを実装している、 ということだけです。 また、 どのような様式の椅子が返ってきたとしても、 同じファクトリー・オブジェクトによって返されるソファーやコーヒーテーブルの様式は常に一致しています。
もう一つ明らかにしておくべきことがあります。 クライアントが抽象インターフェースだけに依存しているとすると、 実際のファクトリー・オブジェクトは何によって作成されるのでしょうか? 通常、 アプリケーションは、 初期化段階でファクトリー・オブジェクトを一つ生成します。 その少し前に、 アプリは構成や環境設定を使用してファクトリーの型を選択します。
構造
-
抽象製品 (Abstract Product) は、 製品ファミリーを構成する個別の関連製品に対するインターフェースを宣言します。
-
具象製品 (Concrete Product) は、 抽象製品の種々の変種ごとにグループ化されています。 個々の抽象製品 (椅子とかソファー) は、 全部の変種 (ビクトリア調、 現代風) において実装されている必要があります。
-
抽象ファクトリー (Abstract Factory) インターフェースは、 抽象製品のそれぞれを生成するメソッドの集合です。
-
具象ファクトリー (Concrete Factory) は、 抽象ファクトリーの生成メソッドを実装します。 個々の具象ファクトリーは、 特定の異種 (様式) に対応しており、 そのような異種の製品のみを作成します。
-
具象ファクトリーは、 具象製品をインスタンス化しますが、 生成メソッドのシグネチャーは、 抽象 製品である必要があります。 そうすることで、 クライアント側コードは、 あるファクトリーから返される特定の変種の製品に縛られることがなくなります。 抽象インターフェースによって通信する限り、 クライアント (Client) は、 いかなる具象ファクトリーや製品変種も扱うことができます。
擬似コード
ここでは、 Abstract Factory パターンをどう使用すれば、 クライアントを具象 UI クラスに結合することなくプラットフォーム互換の UI 部品の生成に利用できるかを説明します。 結合されてないにもかかわらず、 選択したオペレーティングシステムに適した UI 部品が生成されます。
プラットフォーム互換アプリケーションでは同一 UI 部品は、 オペレーティングシステムにより若干見た目に違いはありますが、 似たように振る舞うことになります。 もっと言うと、 UI 部品を既存のオペレーティングシステムのスタイルに一致させることは、 プログラマーであるあなたの責任です。 Windows で走行している時に macOS コントロールをレンダリングするのは避けたいものです。
Abstract Factory インターフェースは、 クライアント側コードが種々の UI 部品を作り出すために使用できる生成メソッドを宣言します。 各種オペレーティングシステムに対応した具象クラスを用意し、 それぞれのオペレーティングシステムに一致した UI 部品を生成します。
仕組みはこうです: アプリケーションは起動時に現在のオペレーティングシステムを検出します。 アプリはこの情報に基づいて、 オペレーティングシステムに適したファクトリー・オブジェクトを生成します。 残りのコードは、 このファクトリーを使用して UI 部品を生成します。 これで、 間違った部品が作成されるのを防ぐことができます。
この手法を使うと、 クライアント側コードが、 ファクトリーや UI 部品の具象クラスやに依存するのを防ぐことができます。 そのためには、 これらのオブジェクトが抽象インターフェースを使用して行われる必要があります。 もう一つのメリットとして、 将来新しいファクトリーや UI 部品が追加されても、 同じクライアント側コードが使えます。
その結果、 アプリに UI 部品の変種を追加しても、 クライアント側コードには手を加える必要がなくなります。 新規部品に対応した新しいファクトリークラスを書き、 そのクラスを選択できるように初期化コードを少し変えるだけですみます。
適応性
関連する製品の集まりである様々な変種に対応したいが、 製品の具象クラス (それは設計段階では未知かもしれません) に依存させたくない場合に、 Abstract Factory を使用します。 あるいは、 単に将来の拡張に備えるために使用することもできます。
Abstract Factory では、 製品ファミリーの各クラスのオブジェクトを作成するインターフェースを使用します。 このインターフェースによってオブジェクトを生成する限り、 すでにアプリで作成した製品と合わない間違った変種の製品が作成されることを心配することはありません。
一連のファクトリー・メソッドからなるクラスがあり、 それが一次責任をあいまいにしてしまう場合に、 Abstract Factory の使用を検討してください。
よく設計されたプログラムでは、 それぞれのクラスは単一の事項についてのみ責任を持ちます。 ある一つのクラスが複数の製品の型を扱う場合、 ファクトリー・メソッドを抽出して独立したファクトリー・クラスを作るか、 完全な Abstract Factory の実装を行うことをお勧めします。
実装方法
-
明確に区別できる製品の型と、 製品型の変種の表を作ります。
-
全部の製品型について、 抽象製品インターフェースを宣言します。 そして、 全インターフェースを実装した製品の具象クラスを作ります。
-
全抽象製品型に対する生成メソッドを含んだ抽象ファクトリー・インターフェースを宣言します。
-
各変種ごとに、 具象ファクトリー・クラスを実装します。
-
アプリのどこかに、 ファクトリー初期化コードを追加します。 そこでアプリケーションの構成か環境設定に従って具象ファクトリー・クラスのインスタンスを作成します。 製品を構築するすべてのクラスに、 このファクトリー・オブジェクトを渡します。
-
コードをスキャンして、 製品クラスのコンストラクターへの直接の呼び出しを探します。 これらの呼び出しを、 ファクトリー・オブジェクトに対する適切な作成メソッドの呼び出しと置き換えます。
長所と短所
- ファクトリーから得られる製品同士は、 互換であることが保証される。
- 具象製品とクライアント側コードの密結合を防止できる。
- 単一責任の原則。 製品作成コードが一箇所にまとめられ、 保守が容易になる。
- 開放閉鎖の原則。 製品の新しい変種を導入しても、 既存クライアント側コードは動作する。
- パターンの使用に伴い、 多数の新規インターフェースやクラスが導入され、 コードが必要以上に複雑になる可能性あり。
他のパターンとの関係
-
多くの設計は、 まず比較的単純でサブクラスによりカスタマイズ可能な、 Factory Method から始まり、 次第に、 もっと柔軟だが複雑な Abstract Factory や Prototype や Builder へと発展していきます。
-
Builder は、 複雑なオブジェクトを段階的に構築することに重点を置いています。 Abstract Factory は、 関連するオブジェクトの集団を作成することに特化しています。 Abstract Factory がすぐにプロダクトを返すのに対して、 Builder ではプロダクトの取得前に、 いくつかの追加の構築のステップを踏まなければなりません。
-
Abstract Factory クラスは、 多くの場合 Factory Methods の集まりですが、 Prototype を使ってメソッドを書くこともできます。
-
サブシステムがオブジェクトを作成する方法をクライアントから隠蔽することだけが目的なら、 Abstract Factory を Facade の代わりに使えます。
-
Abstract Factory は、 Bridge と一緒に使用できます。 この組み合わせは、 Bridge によって定義された抽象化層のいくつかが特定の実装としか動作しない場合に便利です。 この場合、 Abstract Factory はこれらの関係をカプセル化し、 クライアント・コードから複雑さを隠すことができます。
-
Abstract Factories、 Builders、 Prototypes はどれも Singletons で実装可能です。
追記
- 多種のファクトリー・パターンの比較と概念についてはファクトリー比較を参照してください。