web-dev-qa-db-ja.com

アプリケーションのルートでのみ依存関係注入コンテナーを使用しますか?

通常は、次の例のように、c#で依存関係注入コンテナー(unity)を使用します。

class SomeClass
{
    private readonly ILogger _logger;

    public SomeClass()
    {
        _logger = DependencyContainer.Resolve<ILogger>();
    }

    public void DoSomething()
    {
        var someOtherClass = DependencContainer.Resolve();
        someOtherClass().whatElseEver();
    }
}

昨日、diコンテナーを使用した正しい依存関係注入に関する記事をいくつか読みました。この後、私は私の例が完全に悪いことを知っています。これは依存関係の注入ではなく、サービスロケーターのようなものです。

さて、これを解決する方法は?私はコンストラクター注入でこの問題を簡単に解決できると思います:

class SomeClass
{
    private readonly ILogger _logger;
    private readonly ISomeOtherClass _someOtherClass;

    public SomeClass(ILogger logger, ISomeOtherClass someOtherClass)
    {
        _logger = logger;
        _someOtherClass = someOtherClass;
    }

    public void DoSomething()
    {
        _someOtherClass().whatElseEver();
    }
}

これで、依存性注入の原則が正しく実装されました。しかし、これらすべての依存関係をどのように結び付けるのでしょうか?クラス「SomeClass」の必要なすべての品位を解決する呼び出し元クラスがあります。

class SomeClassCaller
{
    public void DoSomething()
    {
        var logger = DependencyContainer.Resolve<ILogger>();
        var someOtherClass = DependencyContainer.Resolve();

        var someClass = new SomeClass(logger, someOtherClass);
        someClass.DoSomething();
    }
}

しかし、この例では依然、依存関係コンテナーをサービスロケーターとして使用しています。これに関するほぼすべての記事で、依存関係コンテナはアプリケーションのルート/エントリポイントでのみ使用する必要があることを読みましたが、これは正しいですか?

つまり、ロガーのように、「サービスロケータ」を使用して一部の依存関係をオンザフライで解決する機能をクラスに持たせることはできません。この問題を回避するために、コンストラクターを介してほぼすべてのクラスにILoggerを挿入する必要がありますよね?この方法は本当に悪いと感じます。もし私がILoggerを10個のクラスを介してすべてのコンストラクターに渡したとしたら、そうではありませんか。

したがって、複雑な依存関係グラフがある場合は、依存関係コンテナのみを使用する必要がありますか?

依存関係コンテナーを使用する場合、アプリケーションのルートでのみ、誰かがそれがどのように見えるかの例を教えてもらえますか?

4
Marcel Hoffmann

これらのクラスと、コンストラクタへの引数として明示的にリストされているそれらの依存関係を検討してください。

class SomeClass {
    public SomeClass(IFoo foo, IBar bar) {}
}

class Foo : IFoo {
    public Foo(IBaz baz) {}
}

class Bar : IBar {}

class Baz :IBaz {}

グラフは次のようになります。

  • SomeClass
    • IFoo
      • IBaz
    • IBar

これを手動でアセンブルするということは、オブジェクトグラフ全体とその依存関係を持つインスタンスを作成することを意味します。

var baz = new Baz();
var foo = new Foo(baz);
var bar = new Bar();
var someClass = new SomeClass(foo, bar);

IOCコンテナーをコンストラクターインジェクションで使用することは、それほど違いはありません。把握する必要があるのは、一端を引っ張るとグラフが続くことです。

container.add(IFoo, Foo);
container.add(IBar, Bar);
container.add(IBaz, Baz);
container.add(ISomeClass, SomeClass);

// now pull on your "Entry Point"/"Bootstrap":
container.get(ISomeClass);

コンテナは必要に応じて依存関係を解決して挿入します。手作業で解決する必要はありませんが、1つ例外があります。これはエントリポイントです。

2
Bruno Schäpper

答えは、問題を2つの分岐に分割する単一の要素に依存します。

IoCフレームワークを使用しない

アプリケーションルートからアプリケーション全体を単体テストしたい場合は、実際にはアプリケーションのメイン関数でのみDIコンテナーを使用し、依存関係注入を使用してすべてを行に渡します。しかし、私は数年前から開発を続けており、実際に誰かがそうしたいと思うような状況にはまだ至っていません。アプリケーションが全体として機能することを確認するために、統合テストがあります。

クラスに依存性注入を使用するか、内部でその依存関係をインスタンス化するか(newを直接呼び出すか、DIコンテナーを使用するか)を決定する場合、経験則では、クラスを単体テストするかどうかを尋ねます作成しています。

将来クラスを単体テストする場合、または [〜#〜] tdd [〜#〜] アプローチを使用して開発している場合は、DIが適しています。作成するクラスの単体テストを気にしない場合、それは他の20個の小さなテスト可能なクラスのラッパーであり、プロシージャとして機能するラッパーは、その依存関係を内部でインスタンス化します。

IoCフレームワークを使用している

依存関係を解決できるIoCフレームワークを使用している場合(Javaの場合は Guice 、C#の場合は Ninject 、PHPの場合は Dice など) )、すべてに依存性注入を使用します

インスタンス化は1か所にあります。これはすばらしいことです。コードの大きなチャンクをユニットテストすることにした場合でも、それを行うことができます。

2
Andy