web-dev-qa-db-ja.com

ファクトリーと静的メソッド

したがって、純粋主義者によるoopについて私が読んだほとんどすべての投稿は、静的メソッドの使用が反パターンであり、コードのテスト容易性を損なうことについて強調し続けています。一方、特にオブジェクトの構築を目的として、(プログラミング言語に関係なく)ファクトリーを使用してサンプルコードを探すたびに、構築されたオブジェクトを返すファクトリクラスの静的メソッドが表示されます。 (以下の疑似コード)

class ProductFactory() {

    public static function make(string name): Product
    {
        if(name contains TV)
            return new TVProduct(name)
        else 
            return new HomeAppliance(name);
    }
}

product = ProductFactory->make('LCD TV');

最終的に私はオブジェクトのインスタンスが必要であり、以下のケースのようにファクトリのインスタンスは必要ないので、私にはこれは完璧に見えます。

productFactory = new ProductFactory();
product = productFactory->make('LCD TV');

私の質問は2つあります。

1-工場を使用する実際の方法は何ですか?工場での静的メソッドはそれらを使用するための適切で受け入れられた方法ですか?

2-静的メソッドを使用するファクトリの単体テストをどのように記述しますか?

オブジェクト構築にファクトリーを使用するという私の理解が基本的なレベルで欠陥がないことを願っています。

3
lahory

名前に「factory」が含まれる2つの異なるパターンが実際にあり、それらは異なる目的を果たします。

  • factory methodまたはstatic factoryパターンは、コンストラクタの代わりに静的メソッドを使用して、より意味のある名前やオブジェクトを作成するためのより便利な方法を提供します。別のサブタイプを返すことも可能です(コンストラクターでは不可能)。ただし、コンパイル時にすべてのサブタイプに結合されます。

  • abstract factoryパターンは、クライアントがコンパイル時に実際の実装から完全に分離されるように、ファクトリインターフェースを定義する場所です。次に、実行時にファクトリ実装を選択すると、クライアントは具象型に結合しなくても、インターフェースを介してオブジェクトを作成できます。

単体テストに関しては、静的ファクトリーメソッドは直接コンストラクター呼び出しとまったく同じです。それらは(ブードゥーマジックに頼らずに)モックアウトすることはできませんが、値など、モックを気にしないタイプと一緒に使用するのが一般的です。オブジェクト。それよりも複雑なもの(特に外部依存関係を含むもの)の場合は、ファクトリインターフェースを作成し、それをクライアントに挿入します(ユニットテストでファクトリをモックアウトします)。

2
casablanca

そうでなければ「良い」コードには静的メソッドの例がたくさんありますが、はい、一般的に静的を使用することは悪い習慣と考えられています。ここで触れている主な論点の1つは、テストが難しくなることです。

逆の順序で質問に答えるために...

  1. 単体テストをどのように記述しますか?

まああなたは工場自体を問題なくテストできますが、ファクトリを使用する何かをテストできますか?または、ファクトリによって内部的に返されたオブジェクトを使用する何か、そのオブジェクトをモックしたいですか?

不可能とは言いませんが、トリックなしでは不可能です。

  1. 私の工場は静的メソッドを使用する必要がありますか?

いいえ。静力学が引き起こす潜在的な問題の最も簡単な解決策は、静力学を使用しないことです。あなたのコードは次に変更されます

product = ProductFactory->make('LCD TV');

product = this.injectedIProductFactory->make('LCD TV');

物事のスキームで支払うのにそれほど大きな代償はありません。

1
Ewan

別の質問で提案されたアプローチの代わりに、もう少しハードコーディングされているが、まだ「抽象的な」コードがある可能性があります。

class ProductFactory() {

    // here factories are injected; could be real
    public ProductFactory(ITVProductFactoru tvProductFactory, IHomeApplianceFactory homeApplianceFactory)
    {
        this->tvProductFactory = tvProductFactory;
        this->homeApplianceFactory = homeApplianceFactory;
    }

    public make(string name): Product
    {
        if(name contains TV)
            return this->tvProductFactory->make(name)
        else 
            return this->homeApplianceFactory->make(name);
    }
}

そのため、コンポジションルートでは選択ロジックを用意する必要はなく、抽象コードに移動して、たとえばテストすることができます。

1
max630

あなた自身の質問に答えました

サンプルコードを探すたびに

例を探すと、プロダクションコードで使用するためではなく、それを説明するために簡略化されたコードが表示されます。

静的ファクトリーを使用して単純化した上記の完全な例では、通常、次のことを行う必要があります。

  • インターフェースを定義する
  • そのインターフェースの実装を定義する
  • dI/IoCコンテナーのセットアップ
  • この実装に注入される複数のプロバイダーの記述を使用する
  • 型によって解決される型に固有の各プロバイダーを実装します(つまり、TVProductProviderのインスタンス化と初期化を処理するTVProduct型のHomeApplianceProviderHomeApplianceについても同じです
  • これらの提供物をDI/IoCに登録する
  • このプロバイダーを反復するファクトリを実装し、特定の製品を処理できるものを決定します(つまり、productProvider->canHandle(name)、およびtrueの場合、必要なすべてのプロバイダーでproductProvider->create(...)を呼び出します。

これだけでも、投稿やチュートリアルが扱っていた実際の例を含めずに、複数ページのサンプルコードと説明が必要です。したがって、ほとんどのチュートリアルはそれを簡素化します。

チュートリアルと同じですが、次のようなコードを提供します

Connection connection = new Connection("server=localhost;user=root;password=p45$w0rD");
... // some query

「ソースに接続文字列をハードコーディングする必要がある」ことを示唆しないように、周囲のコードを説明します。

常識はそれを禁じます。

0
Tseng