Autumn SALE

Factory Method

別名:Virtual Constructor、ファクトリー・メソッド、バーチャル・コンストラクター、仮想コンストラクター

一言でいうと

Factory Method ファクトリー・メソッド 生成に関するデザインパターンの一つで スーパークラスでオブジェクトを作成するためのインターフェースが決まっています しかし サブクラスでは作成されるオブジェクトの型を変更することができます

Factory Method パターン

問題

物流管理アプリケーションを作成するとします アプリの最初のバージョンは トラック輸送のみを処理できます コードの大部分は Truck トラック クラス内に存在します

しばらくして このアプリは かなり人気が出ます 海運会社から アプリに海上物流を組み込んでほしいという要望が毎日数十件寄せられるようになりました

新規の運輸クラスのプログラムへの追加による弊害

コードの大部分がすでに既存のクラスと密に結合されていると 新しいクラスのプログラムへの追加はあまり容易ではない

素晴らしいことです でもコードの方はどうしましょう 現状では コードのほとんどは Truck クラスに結合されています アプリに Ships を追加するには コードの大部分に手を入れる必要があります さらに 後でアプリに別の種類の輸送手段を追加することにした場合 おそらくこの変更作業すべてを再度行うことになるでしょう

結果として 運輸オブジェクトのクラスに応じてアプリの動作を切り替える条件文だらけの かなり厄介なコードができてしまうでしょう

解決策

Factory Method パターンに従うと new 演算子を使用した直接のオブジェクト作成呼び出しを 特別な・メソッドへの呼び出しで置き換えます ご心配なく それでもオブジェクトは new 演算子で作成されます ただそれはファクトリー・メソッド内で呼び出されます ファクトリー・メソッドから返されるオブジェクトはよくと呼ばれます

クリエーター・クラスの構造

サブクラスは ファクトリー・メソッドから返されるオブジェクトのクラスを変更できる

一見すると この変更は無意味に見えるかもしれません コンストラクターの呼び出しをプログラムのある部分から別の部分に移動しただけです しかし これにより サブクラスのファクトリー・メソッドを上書きさえすれば作成されるプロダクトのクラスの変更が可能というメリットがあります

しかし わずかな制限があります これらのプロダクトに共通のベースクラスまたはインターフェースがある場合にのみ サブクラスは 異なる型のプロダクトを返すことができます また ベースクラス内のファクトリー・メソッドの戻り値の型は このインタフェースとして宣言されている必要があります

プロダクト階層の構造

すべてのプロダクトは 同じインターフェースに従う必要がある

たとえば deliver 配送 というメソッドが宣言された Transport 運送 というインターフェースを TruckShip の両クラスが実装します Truck は陸上で Ship は海上で貨物を届けるというように それぞれのクラスでこのメソッドの実装が異なっています Road­Logistics 陸路物流 クラスのファクトリー・メソッドは Truck オブジェクトを返し Sea­Logistics 海上物流 クラスのファクトリー・メソッドは Ship を返します

Factory Method パターン適用後のコードの構造

すべてのプロダクト・クラスが共通のインターフェースを実装している限り どのクラスのオブジェクトでも問題なくクライアント・コードに渡すことができる

ファクトリー・メソッドを使用するコード コードとよく呼ばれる からは 様々なサブクラスが返す実際のプロダクトの間に違いは見られません クライアントはすべての製品を抽象的な Transport として扱います クライアントは すべての Transport オブジェクトが deliver メソッドを持っていることは知っていますが それが厳密にどのように振る舞うかは クライアントにとっては重要なことではありません

構造

Factory Method パターンの構造Factory Method パターンの構造
  1. プロダクト Product クリエーターとそのサブクラスによって生成されるすべてのオブジェクトに共通なインターフェースを宣言します

  2. 具象プロダクト Concrete Product プロダクトのインターフェースの種々の異なる実装です

  3. クリエーター Creator クラスは 新しいプロダクトのオブジェクトを返すファクトリー・メソッドを宣言します このメソッドの戻り値の型がプロダクトのインターフェースと一致していることが要点です

    ファクトリー・メソッドを abstract と宣言して すべてのサブクラスに独自のメソッドの実装を強制することができます 代わりの方法としては 基底クラスのファクトリー・メソッドで 何らかのデフォルトのプロダクト型を返すようにもできます

    その名前にもかかわらず プロダクトの作成はクリエーターの主要任務ではありません 通常 クリエーター・クラスはすでにプロダクトに関連するいくつかの中核となるビジネス・ロジックを持っています ファクトリー・メソッドは このロジックを具象クラスから分離するのに役立ちます 比喩を使うとこういうことです 大規模ソフトウェア開発会社にはプログラマーのための研修部門があるが 会社全体の主な機能は コードを書くことであって プログラマーを育成することではない

  4. 具象クリエーター Concrete Creator 異なる型のプロダクトを返すように 基底クラスのファクトリー・メソッドを上書きします

    ファクトリー・メソッドは 常に新しいインスタンスを作成する必要はないことに注意してください キャッシュ オブジェクト・プール その他の方法で既存のオブジェクトを返してもかまいません

擬似コード

この例では クライアント・コードを具体的な UI クラスと結合することなく プラットフォーム互換な UI 部品を作成するために Factory Method を適用する方法を説明します

Factory Method パターン例の構造

プラットフォーム互換ダイアログの例

基底クラスである Dialog ダイアログ クラスは ウィンドウを描画するために異なる UI 部品を使用します オペレーティングシステムよって これらの UI 部品は少し異なって見えるかもしれませんが それでも一貫して動作する必要があります Windows 上でのボタンは Linux 上でもボタンです

Factory Method を適用すると オペレーティングシステムごとにダイアログのロジックを書き直す必要はありません ダイアログの基底クラス内でボタンを生成するファクトリー・メソッドを宣言しておけば ファクトリー・メソッドから Windows 形式のボタンを返すようにした ダイアログのサブクラスを後で作成できます サブクラスは基底クラスからダイアログのコードの大部分を継承しますが ファクトリー・メソッドのおかげで Windows のボタンをスクリーン上に描画できます

このパターンが機能するためには ダイアログの基底クラスは抽象的ボタン すべての具象的ボタンが従う基底クラスかインターフェース と機能する必要があります こうしておけば どのような種類のボタンであっても ダイアログのコードは機能し続けます

勿論このやり方は 他の UI 部品にも適用できます ただし ダイアログに新しいファクトリ・メソッドを追加するたびに Abstract Factory 抽象ファクトリー に近いものとなっていきます でも大丈夫です このパターンについては後で説明します

// クリエーター・クラスは、ファクトリー・メソッドを宣言。これは、プロダク
// トのクラスのオブジェクトを返さなければならない。クリエーターのサブクラ
// スは通常このメソッドを実装する。
class Dialog is
    // クリエーターは、また、ファクトリー・メソッドの何らかのデフォルトの
    // 実装を提供してもよい。
    abstract method createButton():Button

    // その名前にもかかわらず、プロダクトの作成はクリエーターの主要任務で
    // はない。通常、クリエーター・クラスにはすでにファクトリー・メソッド
    // から返されるプロダクト・オブジェクトに依存した何らかの中核となるビ
    // ジネス・ロジックが存在している。サブクラスは、ファクトリー・メソッ
    // ドを上書きし、異なる種類のプロダクトを返すようにすることで、間接的
    // にビジネス・ロジックを変更可能。
    method render() is
        // プロダクト・オブジェクトを作成するためにファクトリー・メソッド
        // を呼ぶ。
        Button okButton = createButton()
        // そして今、そのプロダクトを使用。
        okButton.onClick(closeDialog)
        okButton.render()


// 具象クリエーターはファクトリー・メソッドを上書きして、結果のプロダクト
// の型を変更。
class WindowsDialog extends Dialog is
    method createButton():Button is
        return new WindowsButton()

class WebDialog extends Dialog is
    method createButton():Button is
        return new HTMLButton()


// プロダクト・インターフェースは、全具象プロダクトが実装しなければならな
// い操作を宣言。
interface Button is
    method render()
    method onClick(f)

// 具象プロダクトは、プロダクト・インターフェースのさまざまな実装を行う。
class WindowsButton implements Button is
    method render(a, b) is
        // ボタンを Windows 風に描画。
    method onClick(f) is
        // OS 本来のクリック・イベントと結びつける。

class HTMLButton implements Button is
    method render(a, b) is
        // ボタンを HTML で表現したものを返す。
    method onClick(f) is
        // ウェブブラウザーのクリック・イベントと結びつける。


class Application is
    field dialog: Dialog

    // アプリケーションは、現在の構成または環境設定に依存して、クリエー
    // ターの型を決定。
    method initialize() is
        config = readApplicationConfigFile()

        if (config.OS == "Windows") then
            dialog = new WindowsDialog()
        else if (config.OS == "Web") then
            dialog = new WebDialog()
        else
            throw new Exception("Error! Unknown operating system.")

    // クライアント・コードは、基底インターフェースを通してではあるが、具
    // 象クリエーターのインスタンスに対して動作する。クライアントが基底イ
    // ンターフェースを介してクリエーターと作業を続けている限り、いかなる
    // クリエーターのサブクラスでも渡すことが可能。
    method main() is
        this.initialize()
        dialog.render()

適応性

コードが機能する対象のオブジェクトの正確な型と依存関係が前もってわからない場合に Factory Method を使用します

Factory Method では プロダクト作成のコードを実際にプロダクトを使用するコードから分離します したがって プロダクト作成のコードの拡張が残りのコードから独立して簡単に行えます

たとえば アプリに新しいプロダクトの型を追加するには 新しい作成クラスのサブクラスを作成し その中のファクトリー・メソッドを上書きするだけですみます

自分の書いたライブラリーやフレームワークのユーザーに内部のコンポーネントを拡張する方法を提供したい場合 Factory Method を使用します

ライブラリーやフレームワークのデフォルト動作を拡張する最も簡単な方法は おそらく継承です しかし フレームワークは 標準コンポーネントの代わりにサブクラスを使用すべきだということをどう認識するのでしょうか

解決策は フレームワーク中に散らばった コンポーネント構築コードを削減し 単一のファクトリー・メソッドに集めることです こうすれば コンポーネント自体を拡張することに加えて 誰でもこのメソッドを上書きできます

それでは それがどううまくいくのか見てみましょう あるオープン・ソースの UI フレームワークを使ってアプリを書くことを想像してみてください アプリには丸いボタンが必要ですが フレームワークは四角いボタンしか提供していません そこで 標準の Button クラスを拡張して 輝かしい Round­Button サブクラスを作成します しかし ここで デフォルトのボタンの代わりに新しいボタンのサブクラスを使用するように メインの UIFramework クラスに伝える必要が出てきます

そのためには フレームワークの基底クラスから UIWith­Round­Buttons 丸いボタンで UI というサブクラスを作り その create­Button メソッドを上書きします このメソッドは基底クラスでは Button オブジェクトを返しますが サブクラスでは Round­Button オブジェクトを返すようにします ここで UIFramework の代わりに UIWith­Round­Buttons クラスを使用してください それだけです

毎回再構築する代わりに 既存オブジェクトを再利用してシステム資源を節約したい場合に Factory Method を使用します

データベース接続 ファイルシステム ネットワーク資源等 資源を大量に消費するオブジェクトを扱う場合に このような状況によく直面します

オブジェクトの再利用のために何をしなければいけないか考えてみましよう

  1. まず 作成されたすべてのオブジェクトを追跡するための記録場所を作成する必要があります
  2. 誰かがオブジェクトを要求してきたら プログラムはそのプール 蓄積地 内の空きオブジェクトを探す必要があります
  3. そして クライアントのコードに対して見つかったオブジェクトを返します
  4. 再使用可能なオブジェクトがない場合 プログラムは新しいオブジェクトを作成し そしてプールに追加する必要があります

これは結構な量のコードになります 重複したコードでプログラムを汚染しないように このコードは一箇所に納めるべきです

おそらく このコードを置くべき最も明白で便利な場所は オブジェクトの再利用をしたいクラスのコンストラクター内です しかし コンストラクターは定義により常に 新規オブジェクトを返さなければなりません 既存インスタンスを返すことはできません

というわけで 新規オブジェクト作成も既存オブジェクトの再利用もできる通常メソッドが必要となります それ ファクトリー・メソッドのように聞こえますね

実装方法

  1. すべてのプロダクトが同じインターフェースに従うようにします このインターフェースでは すべてのプロダクトにとって意味のあるメソッドを宣言する必要があります

  2. クリエーター・クラス内に空のファクトリー・メソッドを追加します メソッドの戻り値の型は 共通のプロダクト・インターフェースと一致する必要があります

  3. クリエーターのコード内で プロダクトのコンストラクターの参照を全部探し出します プロダクト作成コードをファクトリー・メソッドに抽出しながら 一つずつ コンストラクター呼び出しをファクトリー・メソッドへの呼び出しで置き換えていきます

    ファクトリー・メソッドに プロダクトの戻り値の型を決めるためのパラメーターを一時的に追加する必要があるかもしれません

    この時点では ファクトリー・メソッドのコードはあまりきれいなものではないかもしれません どのプロダクトのクラスをインスタンス化するかを選択するかを決める巨大な switch 文の塊になっているかもしれません しかし これはすぐ修正するので 心配は無用です

  4. 次に ファクトリー・メソッドに並んでいるプロダクトの型ごとに クリエーターのサブクラスを作成します 基底クラスのファクトリー・メソッドの構築コードの該当部分を抽出して それでサブクラスのファクトリー・メソッドを上書きします

  5. プロダクトの型が多すぎて すべての型に応じたサブクラスを作成することがあまり現実的ではない場合は 基底クラスに追加した制御パラメーターを再利用できます

    たとえば 次のようなクラスの階層があるとします 基底クラスである Mail 郵便 クラスとそのサブクラス Air­Mail 航空便 Ground­Mail 陸送便 Transport 運輸 クラスとして Plane 飛行機 Truck トラック Train 鉄道 Air­Mail クラスは Plane オブジェクトのみを使用しますが Ground­MailTruckTrain 両方のオブジェクトを扱うことができます 両方のケースを扱うために新しいサブクラス たとえば Train­Mail 鉄道便 を作ることもできます もう一つ選択肢としては クライアント・コードが Ground­Mail クラスのファクトリー・メソッドを引数として渡して どちらのプロダクトを受け取りたいかを伝えるようにすることです

  6. すべての抽出作業の後 基底クラスのファクトリー・メソッドが空になった場合は そのクラスを抽象クラスとすることができます 何か残った場合は それをメソッドのデフォルト動作とすることができます

長所と短所

  • クリエーターと具象プロダクトとの密な結合を回避
  • プロダクト作成コードがプログラム中の一箇所にまとめられ 保守が容易
  • プロダクトの新しい型をプログラムに導入しても 既存のクライアント・コードの機能に影響しない
  • 本パターンの適用では 多数の新規サブクラス導入の必要があり コードの複雑化の恐れあり 既存クリエーター・クラスの階層にこのパターンを適用する場合 最善の結果が得られる

他のパターンとの関係

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

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

  • Factory MethodIterator と一緒に使って コレクションのサブクラスが コレクションと互換な 異なる型のイテレーターを返すようにできます

  • Prototype は継承に基づいていないので 継承の欠点はありません 一方 Prototype クローンされたオブジェクトの複雑な初期化が必要となります Factory Method は継承に基づいていますが 初期化のステップは必要ありません

  • Factory Method Template Method の特別な場合です 同時に Factory Method 大きな Template Method の一つのステップとして使うこともできます

コード例

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

追記

  • 多種のファクトリー・パターンとその概念の違いがはっきりしない場合は ファクトリー比較を参照してください