web-dev-qa-db-ja.com

依存性注入とサービスの場所

現在、DIとSLの長所と短所を比較検討しています。しかし、私は次のキャッチ22に自分自身を見つけました。これは、すべてにSLを使用し、各クラスにIoCコンテナーのみを挿入する必要があることを意味します。

DIキャッチ22:

Log4Netなどの一部の依存関係は、DIには適していないだけです。私はこれらのメタ依存性を呼び出し、コードの呼び出しに対して不透明であると感じています。私の正当化は、単純なクラス「D」が元々ロギングなしで実装され、その後ロギングを必要とするようになった場合、依存クラス「A」、「B」、および「C」は何らかの方法でこの依存関係を取得してそれを渡す必要があるということです「A」から「D」(「A」が「B」を構成し、「B」が「C」を構成すると仮定) 1つのクラスにログインする必要があるという理由だけで、コードを大幅に変更しました。

したがって、メタ依存関係を取得するには不透明なメカニズムが必要です。 2つ思い浮かぶのは、シングルトンとSLです。前者には、主に厳格なスコープ機能に関する既知の制限があります:せいぜい、シングルトンは、アプリケーションスコープ(つまり、静的変数)に格納されている抽象ファクトリを使用します。これにより、ある程度の柔軟性が得られますが、完全ではありません。

より良い解決策は、そのようなクラスにIoCコンテナーを挿入し、そのクラス内からSLを使用して、コンテナーからこれらのメタ依存関係を解決することです。

したがって、キャッチ22:クラスには現在IoCコンテナーが注入されているので、他のすべての依存関係も解決するためにそれを使用してみませんか?

私はあなたの考えを大いに感謝します:)

37

クラスには現在IoCコンテナーが挿入されているので、他のすべての依存関係も解決するために使用しないのはなぜですか?

サービスロケーターパターンを使用すると、依存性注入の主なポイントの1つが完全に無効になります。依存性注入のポイントは、依存性を明示的にすることです。コンストラクターで明示的なパラメーターを作成しないことでこれらの依存関係を非表示にすると、本格的な依存関係の注入は行われなくなります。

これらはすべて、Foo(ジョニーキャッシュの歌のテーマに設定)という名前のクラスのコンストラクターです。

違う:

public Foo() {
    this.bar = new Bar();
}

違う:

public Foo() {
    this.bar = ServiceLocator.Resolve<Bar>();
}

違う:

public Foo(ServiceLocator locator) {
    this.bar = locator.Resolve<Bar>();
}

正しい:

public Foo(Bar bar) {
    this.bar = bar;
}

後者のみがBarへの依存を明示的にします。

ロギングに関しては、ドメインコードに浸透することなくそれを行う正しい方法があります(そうしないと、依存性注入期間を使用します)。驚くべきことに、IoCコンテナーはこの問題を解決できます。開始 ここ

59
jason

Service Locatorは、 http://blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx で詳しく説明されている理由により、アンチパターンです。ロギングに関しては、それを他の依存関係と同様に依存関係として扱い、コンストラクターまたはプロパティインジェクションを介して抽象化をインジェクトでき​​ます。

Log4netとの唯一の違いは、サービスを使用する呼び出し元のタイプが必要なことです。 Ninject(または他のコンテナ)を使用して、サービスを要求している型を見つけるにはどうすればよいですか? これを解決する方法を説明します(Ninjectを使用しますが、どのIoCコンテナにも適用できます)。

あるいは、ロギングを横断的な関心事と考えることもできますが、これはビジネスロジックコードと混合するのには適切ではありません。その場合、多くのIoCコンテナーによって提供されるインターセプトを使用できます。 http://msdn.Microsoft.com/en-us/library/ff647107.aspx Unityでのインターセプトの使用について説明しています。

8
devdigital

私の意見では、それは依存するということです。ある方が良い場合もあれば、ある方が良い場合もあります。しかし、一般的に私はDIを好むと言えます。その理由はほとんどありません。

  1. 依存関係が何らかの形でコンポーネントに挿入されると、そのインターフェイスの一部として扱うことができます。したがって、コンポーネントのユーザーがこの依存関係を提供しやすくなります。挿入されたSLまたは静的SLの場合、依存関係は隠されており、コンポーネントの使用は少し難しくなります。

  2. インジェクトされた依存関係は、単純にモックできるため、単体テストに適しています。 SLの場合は、ロケーターとモックの依存関係を再度セットアップする必要があります。だから、もっと手間がかかります。

5
Oleg Rudckivsky

ロギングは[〜#〜] aop [〜#〜]を使用して実装できるため、ビジネスロジックと混同しないようにできます。

それ以外の場合、オプションは次のとおりです。

  • オプションの依存関係(setterプロパティなど)を使用し、ユニットテストではロガーを挿入しません。 IOC本番環境で実行する場合、コンテナが自動的に設定します。
  • アプリのほぼすべてのオブジェクトが使用している依存関係がある場合(「ロガー」オブジェクトが最も一般的な例です)、シングルトンアンチパターンが良い習慣になる数少ないケースの1つです。一部の人々は、これらの「良いシングルトン」をAmbient Contextと呼びます: http://aabs.wordpress.com/2007/12/31/ the-ambient-context-design-pattern-in-net /

もちろん、このコンテキストは構成可能でなければならないため、ユニットテストにスタブ/モックを使用できます。 AmbientContextのもう1つの推奨される使用法は、ユニットテスト中にスタブできるように現在の日付/時刻プロバイダーをそこに置くことであり、必要に応じて時間を短縮します。

4
frecil

DIを使用しますが、トップレベルの依存関係をオブジェクトにバンドルして、依存関係が変更された場合のリファクタリングを回避します。

以下の例では、派生したすべての依存関係をリファクタリングすることなく「ServiceDependencies」に追加できます。

例:

public ServiceDependencies{
     public ILogger Logger{get; private set;}
     public ServiceDependencies(ILogger logger){
          this.Logger = logger;
     }
}

public abstract class BaseService{
     public ILogger Logger{get; private set;}

     public BaseService(ServiceDependencies dependencies){
          this.Logger = dependencies.Logger; //don't expose 'dependencies'
     }
}


public class DerivedService(ServiceDependencies dependencies,
                              ISomeOtherDependencyOnlyUsedByThisService                       additionalDependency) 
 : base(dependencies){
//set local dependencies here.
}
1
BlackjacketMack

JavaでGoogle Guice DIフレームワークを使用しましたが、テストを簡単にするだけではないことがわかりました。たとえば、application(クラスではない)ごとに個別のログが必要でした。さらに、すべての共通ライブラリコードで現在の呼び出しコンテキストでロガーを使用する必要がありました。ロガーを注入することでこれが可能になりました。確かに、すべてのライブラリコードを変更する必要がありました。ロガーはコンストラクタに挿入されました。最初は、必要なコーディングの変更がすべてあったため、このアプローチに抵抗しました。最終的に、変更には多くの利点があることに気付きました。

  • コードがよりシンプルになりました
  • コードがより堅牢になりました
  • クラスの依存関係が明らかになりました
  • 多くの依存関係があった場合、それはクラスがリファクタリングを必要とすることを明確に示していました
  • 静的シングルトンが排除されました
  • セッションまたはコンテキストオブジェクトの必要性がなくなりました
  • DIコンテナを1つのスレッドのみを含むように構築できるため、マルチスレッドがはるかに簡単になり、不注意による相互汚染が排除されました。

言うまでもなく、私は今ではDIの大ファンであり、最も些細なアプリケーション以外のすべてにDIを使用しています。

1
sabra

私はこの質問が少し古いことを知っています。私は自分の意見を述べたいと思いました。

実際には、10回のうち9回は本当にneed SLではなく、DIに依存する必要があります。ただし、SLを使用する必要がある場合があります。 SL(またはそのバリエーション)を使用して自分自身が見つけた1つの領域は、ゲーム開発です。

(私の意見では)SLのもう1つの利点は、internalクラスをやり取りできることです。

以下に例を示します。

internal sealed class SomeClass : ISomeClass
{
    internal SomeClass()
    {
        // Add the service to the locator
        ServiceLocator.Instance.AddService<ISomeClass>(this);
    }

    // Maybe remove of service within finalizer or dispose method if needed.

    internal void SomeMethod()
    {
        Console.WriteLine("The user of my library doesn't know I'm doing this, let's keep it a secret");
    }
}

public sealed class SomeOtherClass
{
    private ISomeClass someClass;

    public SomeOtherClass()
    {
        // Get the service and call a method
        someClass = ServiceLocator.Instance.GetService<ISomeClass>();
        someClass.SomeMethod();
    }
}

ご覧のとおり、ライブラリのユーザーは、このメソッドが呼び出されたことを知りません。DIを使用しなかったためであり、とにかくできるというわけではありません。

1
Mathew O'Dwyer

これは、Mark Seemanによる「Service Locator is an Anti-Pattern」に関するものです。私はここで間違っているかもしれません。しかし、私も自分の考えを共有すべきだと思った。

public class OrderProcessor : IOrderProcessor
{
    public void Process(Order order)
    {
        var validator = Locator.Resolve<IOrderValidator>();
        if (validator.Validate(order))
        {
            var shipper = Locator.Resolve<IOrderShipper>();
            shipper.Ship(order);
        }
    }
}

OrderProcessorのProcess()メソッドは、実際には「制御の反転」の原則に従っていません。また、メソッドレベルで単一責任の原則を破ります。メソッドがインスタンス化に関係するのはなぜですか

オブジェクト(newクラスまたはS.L.クラスを介して)何でも達成する必要があります。

Process()メソッドでオブジェクトを作成する代わりに、コンストラクターは、以下に示すように、それぞれのオブジェクトのパラメーター(依存関係の読み取り)を実際に持つことができます。次に、Service LocatorをIOCと異なるものにする方法

容器。また、単体テストにも役立ちます。

public class OrderProcessor : IOrderProcessor
{
    public OrderProcessor(IOrderValidator validator, IOrderShipper shipper)
    {
        this.validator = validator; 
        this.shipper = shipper;
    }

    public void Process(Order order)
    {

        if (this.validator.Validate(order))
        {
            shipper.Ship(order);
        }
    }
}


//Caller
public static void main() //this can be a unit test code too.
{
var validator = Locator.Resolve<IOrderValidator>(); // similar to a IOC container 
var shipper = Locator.Resolve<IOrderShipper>();

var orderProcessor = new OrderProcessor(validator, shipper);
orderProcessor.Process(order);

}
1
Aniket Bhoyar

DIの場合、注入された型アセンブリへのハードリファレンスが必要ですか?私は誰もそれについて話していません。 SLの場合、config.jsonなどから必要なときにタイプを動的にロードする場所をリゾルバーに伝えることができます。また、アセンブリに数千のタイプとその継承が含まれている場合、それらを登録するためにサービスコレクションプロバイダーへの数千のカスケード呼び出しが必要ですか?それは私が多くの話を見るところです。ほとんどの人は、DIの利点と、それが.netでどのように実装されるかという点について、ハードリンク型アセンブリへの参照を追加するための拡張メソッドを提示しました。それは私にとってあまり分断されていません。

0
FTLTraveller

この例が依存関係としてlog4netのみを使用する場合、必要なことはこれだけです。

ILog log = LogManager.GetLogger(typeof(Foo));

Log4netはパラメーターとして型(または文字列)を取得することで詳細なログを提供するため、依存関係を挿入する意味はありません。

また、DIはSLと相関していません。私見ServiceLocatorの目的は、オプションの依存関係を解決することです。

例:SLがILogインターフェースを提供する場合、ロギングdaaを作成します。

人々は本当にDIが唯一の良いIOCパターンであると言っていることを知っていますが、私はこれを取得しません。SLを少し売ろうとします。新しいMVCコアフレームワークを使用して表示します最初のDIエンジンは本当に複雑です。人々がDIと言ったときに本当に意味することは、Unity、Ninject、Autofacなどのフレームワークを使用することです。ファクトリクラスを作成します。小規模で高速なプロジェクトの場合、これは簡単な方法ですIOC適切なDIのフレームワーク全体を学習しなくても、学習するのはそれほど難しいことではないかもしれません。 MVCコアドキュメントからの引用を使用します。「ASP.NET Coreは、依存性注入をサポートおよび活用するためにゼロから設計されています。」ほとんどの人は、DIについて「コードベースの99%が古いMVCがDIをサポートしていなかったのに、コードの1%のみがそれに気づく必要がある場合、なぜ彼らはゼロから設計する必要があるのでしょうか?これは大きな問題ですDIのmそれはDIに依存します。すべてを「ITが完了するように」機能させるには多くの作業が必要です。 _[FromServices]_属性を使用する場合、新しいアクションインジェクションを見ると、これはDIに依存しません。これで、DIの人々は、あなたはこのようなものではなく、Factoriesを使うべきだと言うでしょう。 DIの問題は、フィルターでも確認できます。フィルターでDIを取得するために必要なことも確認してください。

_public class SampleActionFilterAttribute : TypeFilterAttribute
{
    public SampleActionFilterAttribute():base(typeof(SampleActionFilterImpl))
    {
    }

    private class SampleActionFilterImpl : IActionFilter
    {
        private readonly ILogger _logger;
        public SampleActionFilterImpl(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogInformation("Business action starting...");
            // perform some business logic work

        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            // perform some business logic work
            _logger.LogInformation("Business action completed.");
        }
    }
}
_

SLを使用した場合、var _logger = Locator.Get();を使用してこれを実行できます。そして、ビューに行きます。 DIに関する善意があるので、ビューにSLを使用する必要がありました。新しい構文_@inject StatisticsService StatsService_はvar StatsService = Locator.Get<StatisticsService>();と同じです。 DIの最も宣伝されている部分は単体テストです。しかし、人々がやっているのは、目的のない模擬サービスをテストするか、実際のテストを行うためにそこにDIエンジンを接続する必要があるだけです。そして、私はあなたが何でも悪いことをすることができることを知っていますが、人々はそれが何であるかを知らなくてもSLロケーターを作ることになります。多くの人が最初に読まずにDIを作るわけではありません。 DIの最大の問題は、クラスのユーザーが他のクラスの内部動作を認識して使用する必要があることです。
SLは優れた方法で使用でき、そのシンプルさのほとんどにいくつかの利点があります。

0
Filip Cordas