web-dev-qa-db-ja.com

コンポジションルート外のIoCコンテナを参照せずに、メソッド呼び出しごとのインスタンス化をサポートするにはどうすればよいですか?

私は興味を持って読みます この記事 はこう述べています:

DIコンテナーは、コンポジションルートからのみ参照する必要があります。他のすべてのモジュールはコンテナへの参照があってはなりません。

これは、すべてのアプリケーションコードがコンストラクターインジェクション(または他のインジェクションパターン)のみに依存しているが、構成されていないことを意味します。アプリケーションのエントリポイントでのみ、最終的にオブジェクトグラフ全体が構成されます。

私は、すべてのインスタンスが注入されるべきであり、IoCコンテナ以外のものによってインスタンス化されるべきではないことを意味します。

このパターンでは、これは許可されていません。

class MyClass {
    void SomeMethod() {
        var w = new Worker();
        w.DoSomething();
    }
}

代わりに、次のようになります。

class MyClass {
    protected readonly IWorker _worker;
    public MyClass(IWorker worker) {
        _worker = worker;
    }
    void SomeMethod() {
        _worker.DoSomething();
    }
}

WorkerのインスタンスごとにMyClassのインスタンスが1つだけ必要な場合、これは正常に機能します。しかし、Workerがステートフル(またはさらに悪いことにDisposable)であり、SomeMethodが呼び出されるたびに新しいインスタンスが必要な場合はどうなりますか?

私はこのようなことをすることができます:

class MyClass {
    protected readonly IUnityContainer _container;
    public MyClass(IUnityContainer container) {
        _container = container;
    }
    void SomeMethod() {
        using (var w = _container.Resolve<IWorker>()) {
            w.DoSomething();
        }
    }
}

しかし、その手法は この記事 によって眉をひそめられます。また、コンテナはコンポジションルートからのみ参照されるべきであるという原則に違反します。

私は工場を注入することができました:

class MyClass {
    protected readonly IWorkerFactory _factory;
    public MyClass(IWorkerFactor factory) {
        _factory = factory;
    }
    void SomeMethod() {
        using (var w = _factory.Create<IWorker>()) {
            w.DoSomething();
        }
    }
}

...これは問題に対処しているようです。しかし、ファクトリを作成するときは、コンテナをもう一度参照する必要があります。

class WorkerFactory : IWorkerFactory
{
    protected readonly IUnityContainer _container;

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

    public T Create<T>() where T : IWorker {
        return _container.Resolve<T>();
    }
}

...それで、私は問題を解決していません。

「DIコンテナーはコンポジションルートからのみ参照する必要がある」というのが本当の場合、メソッド呼び出しごとのインスタンス化でそれがどのように可能であるかはわかりません。

コンポジションルートの外部からコンテナを参照せずに、メソッド呼び出しごとのインスタンス化をどのようにサポートしますか?

6
John Wu

ほとんどのIoCコンテナーは、ファクトリーを自動的に実装する方法を提供します。例を見てください:

IoCコンテナーはファクトリーのインスタンスを作成します。ファクトリーのインスタンスは、適切に構成されている場合、メソッドの呼び出しごとに新しいオブジェクトを返します。

これが機能するのは、コンテナーが、コンテナーを参照するファクトリー用のコードを生成し、それを使用して、作成するオブジェクトの依存関係を満たすためです。 IoCコンテナーがあまり成熟しておらず、その機能がない場合は、独自のファクトリーを作成する必要があり、そのファクトリーはIoCコンテナーを直接参照する必要があります。このケースはルールの例外であり、コンテナへの参照がファクトリの実装で隔離され、どこにも漏れない限り、ルールの精神に従うことになります。

6
Erik

まず、リクエストごとのインスタンス化は、アプリにとってしばしば厄介です。それは大量のゴミを作成します。遅くなる傾向があります。スケーラブルなアプリケーションの場合、それはしばしば回避されます。特にIoCの解決とインスタンス化がリフレクションに影響を与えて問題を引き起こすため、メソッドごとの呼び出しはさらに悪化します。

第二に、Create<T>は実際にはファクトリではなく、ServiceLocatorです。彼らは神のオブジェクトになるまで成長するので、彼らは一般に眉をひそめています。

そうは言っても、IWorkerをIoCコンテナーに入れる代わりに、Func<IWorker>代わりに(または戦略インターフェース、またはより伝統的なファクトリー)。その後、消費者は必要に応じてワーカーをインスタンス化できます。 Its依存関係は静的である必要があり、毎回コンテナに戻る必要がなくなります。代わりに、コンテナーのすべてをセットアップするときに、依存関係が渡されます。

率直に言って、ファクトリーが依存関係を解決するためにコンテナーへの参照を必要とする場合(ファクトリーの実装の詳細でその参照を非表示にしている間)、それは世界で最悪のことではありません。ガイドラインはあなたを助けるためのものであり、手錠ではありません。

4
Telastyn

私はあなたの推論は健全であり、この点を含むと思います:

しかし、ファクトリを作成するときは、コンテナをもう一度参照する必要があります。

_class WorkerFactory : IWorkerFactory
{
    protected readonly IUnityContainer _container;

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

    public T Create<T>() where T : IWorker {
        return _container.Resolve<T>();
    }
}
_

...それで、私は問題を解決していません。

あなたが問題を解決していないことに私は同意しません-Mark Seemannによって提起されたいくつかの問題と、あなたのソリューションがそれらにどのように対処するかを検討してください:

  • コンテナーは、「実際の」ビジネスロジックを含むサービスクラスの依存関係ではなくなりました。
  • WorkerFactoryは、実際にIWorkerを作成する必要があるクラスを特に対象としており、これらのクラスにのみ提供されます。
  • WorkerFactoryはSRPに準拠しています-ビジネス/サービスクラスの短命なIWorkerオブジェクトの作成のみを担当します。
  • ドメイン/サービスクラスのコンストラクターは、依存関係をIUnityContainerインターフェースの背後に隠さなくなりました。
  • RegisterInstanceRegisterTypeなどのメソッドの可視性は最小限に抑えられています。サービスクラスは、IoCコンテナーを改ざんすることによって、こっそりと「不正になる」ことができなくなりました。
  • もっと主観的に言えば、IWorkerFactoryの目的と使用法は、オブジェクトの作成にとってより慣用的に見えます(最小驚きの原則)。

Erikは、AutoFac、Windsor、Ninjectを使用した例にリンクしています。これらは、同じことを行い、手作業のWorkerFactoryを排除するすぐに使用できるソリューションを提供しますが、(おそらく)同じ方法。

すぐに使えるようなUnityのようなIoCコンテナーの場合(これについて間違っている場合は修正してください)、WorkerFactoryのような小さな拡張機能を書くことができるはずですさらに広く再利用可能:

_public class AbstractFactory<T> : IFactory<T>
{
    protected readonly IUnityContainer _container;

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

    public T Create()  {
        return _container.Resolve<T>();
    }
}
_

Unity構成:

_  // I believe this is the correct syntax, but not tested!  
  // the concept should work with Unity anyway.
  container.RegisterType(typeof(IFactory<>), typeof(AbstractFactory<>));
  container.RegisterType(typeof(IWorker), typeof(Worker));
_

存続期間の短いインスタンスを作成する必要があるコードは、ほとんど同じに見えます。

_public class MyClass
{
    private readonly IFactory<IWorker> _workerFactory;

    public MyClass(IFactory<IWorker> workerFactory)
    {
        _workerFactory = workerFactory;
    }
}
_

また関連:Mark Seemannは、SO(彼の答えのリストはこちら: https://stackoverflow.com/questions/2280170/ why-do-we-need-abstract-factory-design-pattern/2280289#2280289 )抽象ファクトリー内の古いnewを提案するいくつかの回答があるようです。

別の関連するノートでは、Markの本 。NETでの依存性注入 は、存続期間の短い依存関係(第6章)、特にライフタイムを管理する必要がある、またはリソースをクリーンアップする必要がある「境界」オブジェクトについて説明していますDbConnectionTcpClient、または「金属の近く」で動作するその他の同様のクラスなど。

Markは彼の本の中で、これらの種類のクラスを注入したり、ファクトリで作成したりすべきではなく、インターフェイスがビジネス上の懸念ではなく技術的な懸念に向けられているため、サービスクラスに公開すべきではないと主張しています。つまりビジネスロジッククラスは、接続文字列、タイムアウトエラー、再試行などでDbConnectionのニュアンスを処理してはなりません。

この本は、これらのタイプの「境界」オブジェクトに対してIoCを完全に無視し、それらを抽象化(理想的にはステートレスな抽象化)でラップすることを提案しています。重要なライフタイム/リソース管理を隠すこれらの抽象化は、低レベルオブジェクトをnew '(およびDispose()' ing)する責任がありますが、IoCコンテナは、サービスクラスへの抽象化。これは、コマンドやクエリの実行などの高レベルの懸念事項のみを対象とします。

2
Ben Cottrell