web-dev-qa-db-ja.com

単一のコンテナーにアクセスするためにシングルトンを作成するか、それをアプリケーションに渡す方が良いですか?

IoCフレームワークを使用することにつま先を浸し、Unityの使用を選択しました。私がまだ完全に理解していないことの1つは、オブジェクトをアプリケーションのより深いところまで解決する方法です。私はそれを明確にする瞬間に電球を持っていなかったと思う。

だから私は疑似コードで次のようなことをやろうとしています

void Workflow(IUnityContatiner contatiner, XPathNavigator someXml)
{
   testSuiteParser = container.Resolve<ITestSuiteParser>
   TestSuite testSuite = testSuiteParser.Parse(SomeXml) 
   // Do some mind blowing stuff here
}

したがって、testSuiteParser.Parseは次のことを行います

TestSuite Parse(XPathNavigator someXml)
{
    TestStuite testSuite = ??? // I want to get this from my Unity Container
    List<XPathNavigator> aListOfNodes = DoSomeThingToGetNodes(someXml)

    foreach (XPathNavigator blah in aListOfNodes)
    {
        //EDIT I want to get this from my Unity Container
        TestCase testCase = new TestCase() 
        testSuite.TestCase.Add(testCase);
    } 
}

3つのオプションが表示されます。

  1. シングルトンを作成して、どこにでもアクセスできるUnityコンテナーを保存します。私は本当にこのアプローチのファンではありません。このような依存関係を追加して依存関係注入フレームワークを使用することは、少し奇妙なことに思えます。
  2. IUnityContainerをTestSuiteParserクラスとそのすべての子に渡します(nレベルの深さ、または実際には約3レベルの深さと想定)。どこでもIUnityContainerを渡すと、奇妙に見えます。私はこれを乗り越える必要があるかもしれません。
  3. Unityを使用する正しい方法で電球の瞬間を持っています。誰かがスイッチのフリックを手伝ってくれることを願っています。

[編集]私が明確にしていないことの1つは、foreachステートメントの反復ごとにテストケースの新しいインスタンスを作成することです。上記の例では、テストスイートの構成を解析し、テストケースオブジェクトのコレクションを入力する必要があります

49
btlog

DIへの正しいアプローチは、Constructor Injectionまたは別のDIパターン(ただし、Constructor Injectionが最も一般的)を使用して、依存関係をコンシューマーに注入することですDIコンテナに関係なく

あなたの例では、依存関係TestSuiteTestCaseが必要なようですので、TestSuiteParserクラスは静的にアナウンスする必要がありますその(唯一の)コンストラクターを通じて要求することで、これらの依存関係が必要であること:

public class TestSuiteParser
{
    private readonly TestSuite testSuite;
    private readonly TestCase testCase;

    public TestSuiteParser(TestSuite testSuite, TestCase testCase)
    {
        if(testSuite == null)
        {
            throw new ArgumentNullException(testSuite);
        }
        if(testCase == null)
        {
            throw new ArgumentNullException(testCase);
        }

        this.testSuite = testSuite;
        this.testCase = testCase;
    }

    // ...
}

readonlyキーワードとGuard句の組み合わせがクラスの不変条件を保護し、正常に作成されたTestSuiteParserのインスタンスが依存関係を利用できるようにする方法に注意してください。

これで、Parseメソッドを次のように実装できます。

public TestSuite Parse(XPathNavigator someXml) 
{ 
    List<XPathNavigator> aListOfNodes = DoSomeThingToGetNodes(someXml) 

    foreach (XPathNavigator blah in aListOfNodes) 
    { 
        this.testSuite.TestCase.Add(this.testCase); 
    }  
} 

(ただし、1つ以上のTestCaseが関係している可能性があると思います。その場合は、単一のTestCaseの代わりに 抽象ファクトリを挿入する を使用できます。)

Composition Root から、Unity(またはその他のコンテナー)を構成できます。

container.RegisterType<TestSuite, ConcreteTestSuite>();
container.RegisterType<TestCase, ConcreteTestCase>();
container.RegisterType<TestSuiteParser>();

var parser = container.Resolve<TestSuiteParser>();

コンテナはTestSuiteParserを解決するときに、コンストラクターインジェクションパターンを理解するので、必要なすべての依存関係を持つインスタンスをAuto-Wiresします。

シングルトンコンテナーの作成またはコンテナーの受け渡しは Service Locator anti-pattern の2つのバリエーションにすぎないため、お勧めしません。

51
Mark Seemann

私はDependency Injectionそして私もこの質問をしました。 DIを気にするのに苦労していました。主に、自分が取り組んでいる1つのクラスだけにDIを適用することに焦点を合わせていたためです。このクラスをインスタンス化する必要がある場所へのコンテナ。これにより、クラスでResolveメソッドを呼び出すことができます。その結果、Unityコンテナーを静的としてグローバルに利用できるようにするか、シングルトンクラスでラップするかについて考えていました。

私はここで答えを読みましたが、何が説明されているのか本当に理解できませんでした。最終的に私が「理解する」のに役立ったのは、この記事です。

http://www.devtrends.co.uk/blog/how-not-to-do-dependency-injection-the-static-or-singleton-container

そして特にこの段落は「電球」の瞬間でした:

"コードベースの99%はIoCコンテナを認識していないはずです。コンテナを使用するのはルートクラスまたはブートストラップのみであり、それでも単一のresolve呼び出しで通常はすべてです依存関係グラフを作成してアプリケーションまたはリクエストを開始するために必要です。 "

この記事は、アプリケーション全体のUnityコンテナーにアクセスするのではなく、アプリケーションのルートにのみアクセスする必要があることを理解するのに役立ちました。したがって、DIの原則を繰り返し適用して、アプリケーションのルートクラスに戻る必要があります。

これが私と同じように混乱している他の人を助けることを願っています! :)

12
BruceHill

アプリケーションの非常に多くの場所でコンテナを直接使用する必要はありません。コンストラクタですべての依存関係を取得し、メソッドからそれらに到達しないようにする必要があります。あなたの例は次のようなものになるでしょう:

public class TestSuiteParser : ITestSuiteParser {
    private TestSuite testSuite;

    public TestSuitParser(TestSuit testSuite) {
        this.testSuite = testSuite;
    }

    TestSuite Parse(XPathNavigator someXml)
    {
        List<XPathNavigator> aListOfNodes = DoSomeThingToGetNodes(someXml)

        foreach (XPathNavigator blah in aListOfNodes)
        {
            //I don't understand what you are trying to do here?
            TestCase testCase = ??? // I want to get this from my Unity Container
            testSuite.TestCase.Add(testCase);
        } 
    }
}

その後、アプリケーション全体で同じように実行します。もちろん、ある時点で何かを解決する必要があります。たとえばasp.net mvcでは、この場所はコントローラーファクトリーにあります。これがコントローラーを作成するファクトリーです。このファクトリーでは、コンテナーを使用してコントローラーのパラメーターを解決します。ただし、これはアプリケーション全体で1か所だけです(より高度な処理を行うと、おそらくさらにいくつかの場所になります)。

CommonServiceLocator という素敵なプロジェクトもあります。これは、特定のコンテナーに依存しないように、すべての一般的なiocコンテナーの共有インターフェースを持つプロジェクトです。

4

サービスコンストラクターの周りに渡される "ServiceLocator"を1つしか持つことができないが、注入先のクラスの意図された依存関係を "宣言"することができた(つまり、依存関係を非表示にしない)...そのように、すべて(? )サービスロケータパターンに対する反対意見は解消することができます。

public class MyBusinessClass
{
    public MyBusinessClass(IServiceResolver<Dependency1, Dependency2, Dependency3> locator)
    {
        //keep the resolver for later use
    }
}

悲しいことに、c#は可変のジェネリックパラメーターを(まだ)禁止しているので、上記は明らかに私の夢の中にのみ存在するため、追加のジェネリックパラメーターが必要になるたびに新しいジェネリックインターフェイスを手動で追加するのは扱いにくいでしょう。

一方、c#の制限にもかかわらず、次の方法で上記を達成できます...

public class MyBusinessClass
{
    public MyBusinessClass(IServiceResolver<TArg<Dependency1, TArg<Dependency2, TArg<Dependency3>>> locator)
    {
        //keep the resolver for later use
    }
}

この方法では、同じことを達成するために追加のタイピングを行うだけで済みます。 TArgクラスの適切な設計(TArgジェネリックパラメーターの無限ネストを可能にするために巧妙な継承が採用されると想定)が与えられた場合、DIコンテナーはIServiceResolverを適切に解決できる。最終的に、アイデアは、注入されるクラスのコンストラクタで見つかった一般的な宣言に関係なく、IServiceResolverのまったく同じ実装を単に渡すことです。

0
Dantte