web-dev-qa-db-ja.com

サービスロケーターのアンチパターンの代替

UnityをC#のIoCとして使用していますが、質問は実際にはUnityとC#に限定されているのではなく、IoC全般です。

私はSOLID原理に従っています。つまり、2つの具象クラス間の依存関係はほとんどありません。しかし、モデルの新しいインスタンスを作成する必要がある場合、それを行うための最良の方法は何ですか?

私は通常、ファクトリを使用してインスタンスを作成しますが、いくつかの選択肢があり、どちらがより良いのか、そしてなぜですか?

単純なファクトリ:

public class FooFactory : IFooFactory
{
    public IFoo CreateModel()
    {
        return new Foo(); // references a concrete class.
    }
}

サービスロケーターファクトリー

public class FooFactory : IFooFactory
{
    private readonly IUnityContainer _container;

    public FooFactory (IUnityContainer container)
    {
        _container = container;
    }

    public IFoo CreateModel()
    {
        return _container.Resolve<IFoo>(); // Service-locator anti-pattern?
    }
}

Func-factory。他のクラスへの依存関係はありません。

public class FooFactory : IFooFactory
{
    private readonly Func<IFoo> _createFunc;

    public FooFactory (Func<IFoo> createFunc)
    {
        _createFunc= createFunc;
    }

    public IFoo CreateModel()
    {
        return _createFunc(); // Is this really better than service-locator?
    }
}

どのIFooFactoryを使用する必要がありますか。その理由は何ですか。より良いオプションはありますか?

上記の例は、概念的なレベルのものであり、SOLID、保守可能なコード、およびサービスロケータの間のバランスを見つけようとします。これが実際の例です:

public class ActionScopeFactory : IActionScopeFactory
{
    private readonly Func<Action, IActionScope> _createFunc;

    public ActionScopeFactory(Func<Action, IActionScope> createFunc)
    {
        _createFunc = createFunc;
    }

    public IActionScope CreateScope(Action action)
    {
        return _createFunc(action);
    }
}

public class ActionScope : IActionScope, IDisposable
{
    private readonly Action _action;

    public ActionScope(Action action)
    {
        _action = action;
    }

    public void Dispose()
    {
        _action();
    }
}

public class SomeManager
{
    public void DoStuff()
    {
        using(_actionFactory.CreateScope(() => AllDone())
        {
           // Do stuff. And when done call AllDone().
           // Another way of actually writing try/finally.
        }
    }
}

なぜファクトリーを使用するのですか?新しいモデルを作成する必要がある場合があるからです。これが必要なシナリオはさまざまです。たとえばマッパー内で、マッパーがマップするオブジェクトよりも寿命が長い場合。工場での使用例:

public class FooManager
{
    private IService _service;
    private IFooFactory _factory;

    public FooManager(IService service, IFooFactory factory)  
    {
        _service = service;
        _factory = factory;
    }

    public void MarkTimestamp()
    {
        IFoo foo = _factory.CreateModel();
        foo.Time = DateTime.Now;
        foo.User = // current user
        _service.DoStuff(foo);
    }

    public void DoStuffInScope()
    {
        using(var foo = _factoru.CreateModel())
        {
            // do stuff with foo...
        }
    }
}
7
smoksnes

まず最初に、サービスロケーターがアンチパターンであるというマントラは、面倒で生産性が高くありません。それらには欠点がありますが、IoCコンテナーがgoodであることを除いて、従来のIoCコンテナーとほとんど同じです。

それはあなたの例に焦点を当てましょう:

単純な工場は良いです。彼らは明確です。テストは簡単です。拡張は簡単です。

サービスロケーターファクトリは、このシナリオ(そして率直に言って、ほとんどのシナリオ)には過剰です。サービスロケーターを使用すると、任意の型からその型のインスタンス(または例外)に解決できるファクトリーを使用できます。ここでは必要ありません。いくつかのIFooを提供できる必要があるだけです。

Func-factoryはここでは奇妙です。 Func<IFoo>isインターフェース(「2つのものが相互作用するポイント」の定義内)であるため、インターフェースは実際には必要ありません。場合によっては、すでにインターフェイスがあり、func-factoryは実際にはファクトリではなく、大文字のIインターフェイスとfunc "インターフェイス"の間のアダプタです。

bestはニーズによって異なります。個人的には、Funcが好きです。シンプルなファクトリのボイラープレートのオーバーヘッドがない一方で、使用において最も柔軟性があります。

そして、私がサービスロケーターをアンチパターンではないと主張するのと同じくらい、それらにはvery限定的なユースケースがあり、あなたの例も来ませんそれに近い。

8
Telastyn

工場は以下の例外です。

  • サービスロケーターなし
  • シングルトンなし
  • グローバル変数なし
  • 密結合の依存関係はありません

ルール。

理論的には、エンタープライズコードは次の2つの主要部分に分かれます。

  1. オブジェクトグラフコンストラクター:クラスがオブジェクトになるようにnewedされ、特定の構成、フラグ、または一般的な知識に基づいてまとめられる場所
  2. ビジネスレイヤー:グループ1に属さないほとんどすべてを含む場所です。ここにビジネスルールを記述します(クリーンなコードの原則に従う場合)IoC(DI for例)クラスの結合度が低く、凝集度が高く、インターフェースが簡潔である必要がある場合

しかし、それでも、工場内にサービスロケータがあることは大きな問題ではありませんが、手動で作成されたファクトリまたは自動生成されたファクトリが、サービスロケータに依存するよりも優れたソリューションであると判明する場合があります(SIライブラリを使用すると、通常、SIライブラリを使用して、必要以上に追加のデータを送信する必要があります。


事例1

アプリケーションプロセス中に変更されない、または変更されたときに変更がアプリケーション全体に反映する必要がある特定の構成を持つ依存関係の場合、サービスロケータは実際には非常に実現可能なアイデアです。

これらのクラスの一部の例は次のとおりです。

  • データベース、
  • キャッシング
  • webサービスのソース。

これらは通常、アプリケーションのグローバル設定であり、変更すると、オブジェクトグラフに大幅な影響を与えます。

たとえば、現在MongoDBを使用していることを構成で示しており、代わりにMySQLを使用するように構成を変更した場合、特定の部分だけでなくアプリケーション全体にMySQLを使用する可能性が高く、アプリケーション全体に知らせたいMySQLからデータをプルし、Mongoを無視します。


事例2

以前に処理されたデータまたはユーザー入力のいずれかの結果に基づいて、ランタイム中に一般的なインターフェースの実装を構築する必要がある場合は、スイッチ/ケースを使用して独自のファクトリを作成し、次の値に基づいて特定の実装を提供する必要があります。ファクトリメソッドに渡されます。

そうすれば、サービスロケータから不要なコードを転送することなく、非常にシンプルで軽量なファクトリを使用できます。

このタイプのファクトリーが使用されるケースの例は、同じインターフェースを実装するが、その上に追加のデータがあり、完全に異なる方法で構築され、異なるサービスが必要な2つのクラスからデータをプルする場合です。それらを取得するために提供されます。

例としては、コレクションのforeachループがあり、実装が使用する決定のフラグを提供します。このforeach内で、単一の反復の値をファクトリに渡します。これにより、特定の実装が構築されますが、コレクション内のすべての値の共通データを取得するために使用できる共有インターフェースが使用されますforeachを実行しています。

0
Andy