春のセール

Abstract Factory

別名:アブストラクト・ファクトリー、抽象ファクトリー

一言でいうと

Abstract Factory 抽象ファクトリー 抽象工場 生成に関するデザインパターンの一つで 関連したオブジェクトの集りを 具象クラスを指定することなく生成することを可能とします

Abstract Factory パターン

問題

家具工場シュミレーターを作ることを想像してみてください コードは以下ようなものを表現するクラスで構成されています

  1. 関連する製品ファミリー たとえば Chair 椅子 + Sofa ソファー + Coffee­Table コーヒーテーブル

  2. このファミリーの様式 たとえば Chair + Sofa + Coffee­Table には以下のような様式のものがあります Modern 現代風 Victorian ビクトリア様式 Art­Deco アールデコ様式

製品の集まりと様式

製品群と様式

様式の一致する家具を製造するには 同じ様式の家具オブジェクトを生成する何らかの方法が必要となります 様式の異なる家具が配達されたら 客は怒りますよね

現代風ソファーは ビクトリア様式の椅子とは合わない

プログラムに新しい製品や製品群を追加する時 既存コードの変更はしたくありません 家具メーカーはカタログを常に更新しますが そのたびに中核のコードを変更するのは避けたいところです

解決策

Abstract Factory パターンでは まず 家具ファミリーの個別製品 椅子 ソファー コーヒーテーブル ごとに 明示的にインターフェースを宣言します 様式別の製品は これらのインターフェースに従って作成します たとえば 椅子の異なる様式に対応する変種 バリエーション を作るには Chair インターフェースの実装を行い コーヒーテーブルの変種を作るには Coffee­Table インターフェースを実装する といった具合です

Chair クラスの階層

同じオブジェクトの異種は全部 単一クラス階層に移動

次に を宣言します これは 製品ファミリー全製品の生成メソッド create­Chair create­Sofa create­Coffee­Table を並べたインターフェースとなります これらのメソッドは このように抽出したChair Sofa Coffee­Table といったインターフェースで表現された 製品の 抽象 型を返す必要があります

_Factory_ クラスの階層

個々の具象ファクトリー・クラスは 製品の様式に対応する

では製品の様式ごとの変種はどうするか というと 製品の様式ごとに Abstract­Factory を基に別々のファクトリー・クラスを作成します ファクトリーは 特定の種類の製品を返すクラスです たとえば Modern­Furniture­Factory 現代風家具ファクトリー Modern­Chair 現代風の椅子 Modern­Sofa 現代風のソファー そして Modern­Coffee­Table 現代風のコーヒーテーブル のオブジェクトのみ作ることができます

クライアント側のコードは ファクトリー 製品ともそれぞれの抽象インターフェースを通して機能します これにより クライアントのコードに渡すファクトリーの型を変更したり クライアントの受け取る製品の様式を変更しても クライアント側コードは問題なく動作します

クライアントは ファクトリーの実際のクラスが何かということを気にする必要はない

クライアントはファクトリーに椅子を生成してほしいとします クライアントは ファクトリーのクラスを気にする必要もなく どういう種類の椅子が返ってくるかを気にする必要もありません 現代風の椅子でも ビクトリア様式の椅子でも Chair という抽象インターフェースを使って 同じように扱います こういうやり方に従うと クライアントが知っておく必要があることは 椅子が何らかの方法で sit­On 腰掛ける メソッドを実装している ということだけです また どのような様式の椅子が返ってきたとしても 同じファクトリー・オブジェクトによって返されるソファーやコーヒーテーブルの様式は常に一致しています

もう一つ明らかにしておくべきことがあります クライアントが抽象インターフェースだけに依存しているとすると 実際のファクトリー・オブジェクトは何によって作成されるのでしょうか 通常 アプリケーションは 初期化段階でファクトリー・オブジェクトを一つ生成します その少し前に アプリは構成や環境設定を使用してファクトリーの型を選択します

構造

Abstract Factory デザインパターンAbstract Factory デザインパターン
  1. 抽象製品 Abstract Product 製品ファミリーを構成する個別の関連製品に対するインターフェースを宣言します

  2. 具象製品 Concrete Product 抽象製品の種々の変種ごとにグループ化されています 個々の抽象製品 椅子とかソファー 全部の変種 ビクトリア調 現代風 において実装されている必要があります

  3. 抽象ファクトリー Abstract Factory インターフェースは 抽象製品のそれぞれを生成するメソッドの集合です

  4. 具象ファクトリー Concrete Factory 抽象ファクトリーの生成メソッドを実装します 個々の具象ファクトリーは 特定の異種 様式 に対応しており そのような異種の製品のみを作成します

  5. 具象ファクトリーは 具象製品をインスタンス化しますが 生成メソッドのシグネチャーは 製品である必要があります そうすることで クライアント側コードは あるファクトリーから返される特定の変種の製品に縛られることがなくなります 抽象インターフェースによって通信する限り クライアント Client いかなる具象ファクトリーや製品変種も扱うことができます

擬似コード

ここでは Abstract Factory パターンをどう使用すれば クライアントを具象 UI クラスに結合することなくプラットフォーム互換の UI 部品の生成に利用できるかを説明します 結合されてないにもかかわらず 選択したオペレーティングシステムに適した UI 部品が生成されます

Abstract Factory パターン例のクラス図

プラットフォーム互換 UI クラスの例

プラットフォーム互換アプリケーションでは同一 UI 部品は オペレーティングシステムにより若干見た目に違いはありますが 似たように振る舞うことになります もっと言うと UI 部品を既存のオペレーティングシステムのスタイルに一致させることは プログラマーであるあなたの責任です Windows で走行している時に macOS コントロールをレンダリングするのは避けたいものです

Abstract Factory インターフェースは クライアント側コードが種々の UI 部品を作り出すために使用できる生成メソッドを宣言します 各種オペレーティングシステムに対応した具象クラスを用意し それぞれのオペレーティングシステムに一致した UI 部品を生成します

仕組みはこうです アプリケーションは起動時に現在のオペレーティングシステムを検出します アプリはこの情報に基づいて オペレーティングシステムに適したファクトリー・オブジェクトを生成します 残りのコードは このファクトリーを使用して UI 部品を生成します これで 間違った部品が作成されるのを防ぐことができます

この手法を使うと クライアント側コードが ファクトリーや UI 部品の具象クラスやに依存するのを防ぐことができます そのためには これらのオブジェクトが抽象インターフェースを使用して行われる必要があります もう一つのメリットとして 将来新しいファクトリーや UI 部品が追加されても 同じクライアント側コードが使えます

その結果 アプリに UI 部品の変種を追加しても クライアント側コードには手を加える必要がなくなります 新規部品に対応した新しいファクトリークラスを書き そのクラスを選択できるように初期化コードを少し変えるだけですみます

// 抽象ファクトリー・インターフェースは、異なる抽象的なプロダクトを返す一
// 連のメソッドを宣言。これらの製品はファミリーと呼ばれ、ある高レベルな
// テーマや概念に関連する。通常同一のファミリーのプロダクト同士は、協力し
// て動作可能。プロダクトのファミリーにはいくつかの異種があり、ある異種の
// プロダクトは別の異種のプロダクトとの互換性を欠く。
interface GUIFactory is
    method createButton():Button
    method createCheckbox():Checkbox


// 具象ファクトリーは、同一の異種に属するプロダクトのファミリーを生成する。
// ファクトリーにより、生成されるプロダクト同士が互換であることが保証さ
// れる。具象ファクトリーのメソッドのシグネチャーは、抽象プロダクトの返却
// を示唆しているが、メソッド内部では具象プロダクトのインスタンスが生成さ
// れる。
class WinFactory implements GUIFactory is
    method createButton():Button is
        return new WinButton()
    method createCheckbox():Checkbox is
        return new WinCheckbox()

// 各具象ファクトリーは、プロダクトの異種に対応。
class MacFactory implements GUIFactory is
    method createButton():Button is
        return new MacButton()
    method createCheckbox():Checkbox is
        return new MacCheckbox()


// プロダクト・ファミリー中の個別のプロダクトは、それぞれその基底インター
// フェースを持つ。プロダクトの全異種は、このインターフェースを実装しなけ
// ればならない。
interface Button is
    method paint()

// 具象プロダクトは、対応する具象ファクトリーが生成する。
class WinButton implements Button is
    method paint() is
        // ボタンを Windows 風に描画。

class MacButton implements Button is
    method paint() is
        // ボタンを macOS 風に描画。

// これは、もう一つのプロダクトの基底インターフェース。全プロダクトは互い
// にやりとりできるが、同一の異種のプロダクト同士間でのみ適切な動作が可能。
interface Checkbox is
    method paint()

class WinCheckbox implements Checkbox is
    method paint() is
        // チェックボックスを Windows 風に描画。

class MacCheckbox implements Checkbox is
    method paint() is
        // チェックボックスを macOS 風に描画。


// クライアント・コードは、ファクトリーやプロダクトと、抽象型、つまり、
// GUIFactory、Button、Checkbox を介してのみ動作する。これにより、ど
// んなファクトリーやプロダクトのサブクラスでもクライアント・コードに機能
// を損なわずに渡すことが可能。
class Application is
    private field factory: GUIFactory
    private field button: Button
    constructor Application(factory: GUIFactory) is
        this.factory = factory
    method createUI() is
        this.button = factory.createButton()
    method paint() is
        button.paint()


// アプリケーションは、現在の構成や環境設定に応じてファクトリーの型を選び、
// 実行時に(通常は初期化処理中に)生成。
class ApplicationConfigurator is
    method main() is
        config = readApplicationConfigFile()

        if (config.OS == "Windows") then
            factory = new WinFactory()
        else if (config.OS == "Mac") then
            factory = new MacFactory()
        else
            throw new Exception("Error! Unknown operating system.")

        Application app = new Application(factory)

適応性

関連する製品の集まりである様々な変種に対応したいが 製品の具象クラス それは設計段階では未知かもしれません に依存させたくない場合に Abstract Factory を使用します あるいは 単に将来の拡張に備えるために使用することもできます

Abstract Factory では 製品ファミリーの各クラスのオブジェクトを作成するインターフェースを使用します このインターフェースによってオブジェクトを生成する限り すでにアプリで作成した製品と合わない間違った変種の製品が作成されることを心配することはありません

一連のファクトリー・メソッドからなるクラスがあり それが一次責任をあいまいにしてしまう場合に Abstract Factory の使用を検討してください

よく設計されたプログラムでは ある一つのクラスが複数の製品の型を扱う場合 ファクトリー・メソッドを抽出して独立したファクトリー・クラスを作るか 完全な Abstract Factory の実装を行うことをお勧めします

実装方法

  1. 明確に区別できる製品の型と 製品型の変種の表を作ります

  2. 全部の製品型について 抽象製品インターフェースを宣言します そして 全インターフェースを実装した製品の具象クラスを作ります

  3. 全抽象製品型に対する生成メソッドを含んだ抽象ファクトリー・インターフェースを宣言します

  4. 各変種ごとに 具象ファクトリー・クラスを実装します

  5. アプリのどこかに ファクトリー初期化コードを追加します そこでアプリケーションの構成か環境設定に従って具象ファクトリー・クラスのインスタンスを作成します 製品を構築するすべてのクラスに このファクトリー・オブジェクトを渡します

  6. コードをスキャンして 製品クラスのコンストラクターへの直接の呼び出しを探します これらの呼び出しを ファクトリー・オブジェクトに対する適切な作成メソッドの呼び出しと置き換えます

長所と短所

  • ファクトリーから得られる製品同士は 互換であることが保証される
  • 具象製品とクライアント側コードの密結合を防止できる
  • 製品作成コードが一箇所にまとめられ 保守が容易になる
  • 製品の新しい変種を導入しても 既存クライアント側コードは動作する
  • パターンの使用に伴い 多数の新規インターフェースやクラスが導入され コードが必要以上に複雑になる可能性あり

他のパターンとの関係

  • 多くの設計は まず比較的単純でサブクラスによりカスタマイズ可能な Factory Method から始まり 次第に もっと柔軟だが複雑な Abstract FactoryPrototypeBuilder へと発展していきます

  • Builder 複雑なオブジェクトを段階的に構築することに重点を置いています Abstract Factory 関連するオブジェクトの集団を作成することに特化しています Abstract Factory がすぐにプロダクトを返すのに対して Builder ではプロダクトの取得前に いくつかの追加の構築のステップを踏まなければなりません

  • Abstract Factory クラスは 多くの場合 Factory Methods の集まりですが Prototype を使ってメソッドを書くこともできます

  • サブシステムがオブジェクトを作成する方法をクライアントから隠蔽することだけが目的なら Abstract FactoryFacade の代わりに使えます

  • Abstract Factory Bridge と一緒に使用できます この組み合わせは Bridge によって定義された抽象化層のいくつかが特定の実装としか動作しない場合に便利です この場合 Abstract Factory はこれらの関係をカプセル化し クライアント・コードから複雑さを隠すことができます

  • Abstract Factories Builders Prototypes はどれも Singletons で実装可能です

コード例

Abstract Factory を C# で Abstract Factory を C++ で Abstract Factory を Go で Abstract Factory を Java で Abstract Factory を PHP で Abstract Factory を Python で Abstract Factory を Ruby で Abstract Factory を Rust で Abstract Factory を Swift で Abstract Factory を TypeScript で

追記

  • 多種のファクトリー・パターンの比較と概念についてはファクトリー比較を参照してください