web-dev-qa-db-ja.com

依存性注入の構成ルートとデコレータパターン

依存性注入を使用すると、デコレータパターンの実装でStackoverflowExceptionが発生します。 DI/IoCの理解から何かが「欠けている」からだと思います。

たとえば、私は現在CustomerServiceCustomerServiceLoggingDecoratorを持っています。どちらのクラスもICustomerServiceを実装し、デコレータクラスは注入されたICustomerServiceを使用するだけですが、CustomerServiceのコードに影響を与えずにロギングを使用できるように、いくつかの単純なNLogロギングを追加します。また、単一責任の原則に違反していません。

ただし、ここでの問題は、CustomerServiceLoggingDecoratorICustomerServiceを実装し、動作するためにICustomerServiceの実装も必要であるため、Unityはそれを解決しようとし続けることです。これにより、スタックがオーバーフローするまで無限ループが発生します。

これらは私のサービスです:

_public interface ICustomerService
{
    IEnumerable<Customer> GetAllCustomers();
}

public class CustomerService : ICustomerService
{
    private readonly IGenericRepository<Customer> _customerRepository;

    public CustomerService(IGenericRepository<Customer> customerRepository)
    {
        if (customerRepository == null)
        {
            throw new ArgumentNullException(nameof(customerRepository));
        }

        _customerRepository = customerRepository;
    }

    public IEnumerable<Customer> GetAllCustomers()
    {
        return _customerRepository.SelectAll();
    }
}

public class CustomerServiceLoggingDecorator : ICustomerService
{
    private readonly ICustomerService _customerService;
    private readonly ILogger _log = LogManager.GetCurrentClassLogger();

    public CustomerServiceLoggingDecorator(ICustomerService customerService)
    {
        _customerService = customerService;
    }

    public IEnumerable<Customer> GetAllCustomers()
    {
        var stopwatch = Stopwatch.StartNew();
        var result =  _customerService.GetAllCustomers();

        stopwatch.Stop();

        _log.Trace("Querying for all customers took: {0}ms", stopwatch.Elapsed.TotalMilliseconds);
        return result;
    }
}
_

私は現在、次のように登録を設定しています(このスタブメソッドは_Unity.Mvc_によって作成されました):

_        public static void RegisterTypes(IUnityContainer container)
        {
            // NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
            // container.LoadConfiguration();

            // TODO: Register your types here
            // container.RegisterType<IProductRepository, ProductRepository>();

            // Register the database context
            container.RegisterType<DbContext, CustomerDbContext>();

            // Register the repositories
            container.RegisterType<IGenericRepository<Customer>, GenericRepository<Customer>>();

            // Register the services

            // Register logging decorators
            // This way "works"*
            container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
            new InjectionConstructor(
                new CustomerService(
                    new GenericRepository<Customer>(
                        new CustomerDbContext()))));

            // This way seems more natural for DI but overflows the stack
            container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();

        }
_

そのため、依存性注入を使用して実際にデコレータを作成する「適切な」方法がわかりません。私はデコレータをMarkSeemannの答えに基づいています ここ 。彼の例では、クラスに渡されるいくつかのオブジェクトを新しくしています。これが私の「動作」*スニペットの動作です。しかし、私は根本的な一歩を逃したと思います。

なぜこのような新しいオブジェクトを手動で作成するのですか?これは、コンテナに解決を行わせるという点を否定しませんか?または、代わりに、この1つのメソッド内でcontain.Resolve()(サービスロケーター)を実行して、すべての依存関係をまだ注​​入する必要がありますか?

私は「コンポジションルート」の概念に少し精通しています。この概念では、これらの依存関係を1つの場所に接続し、アプリケーションの下位レベルにカスケードします。では、_Unity.Mvc_で生成されたRegisterTypes()はASP.NETMVCアプリケーションのコンポジションルートですか?もしそうなら、ここでオブジェクトを直接更新することは実際に正しいですか?

Unityでは通常、自分でコンポジションルートを作成する必要があるという印象を受けましたが、_Unity.Mvc_は、独自のコンポジションルートを作成するという点で例外であり、コントローラーに依存関係を注入できるようです。コンストラクターにICustomerServiceのようなインターフェースがあり、それを行うためのコードを記述していません。

質問:循環依存のためにStackoverflowExceptionsにつながる重要な情報が不足していると思います。依存性の注入/制御の反転の原則と規則に従いながら、デコレータクラスを正しく実装するにはどうすればよいですか?

2番目の質問:特定の状況でのみロギングデコレータを適用したいと思った場合はどうなりますか?したがって、CustomerServiceLoggingDecorator依存関係を希望する_MyController1_があり、_MyController2_に必要なのは通常のCustomerServiceのみである場合、2つの別個の登録を作成するにはどうすればよいですか?私がそうするなら:

_container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();
container.RegisterType<ICustomerService, CustomerService>();
_

次に、1つが上書きされます。つまり、両方のコントローラーにデコレーターが挿入されるか、通常のサービスが挿入されます。どうすれば両方を許可できますか?

編集:循環依存に問題があり、これに対する正しいDIアプローチの理解が不足しているため、これは重複する質問ではありません。私の質問は、リンクされた質問のようなデコレータパターンだけでなく、概念全体に当てはまります。

19
user9993

前文

DIコンテナー(Unityまたはその他)で問題が発生した場合は、次のことを自問してください。 努力する価値のあるDIコンテナーを使用していますか?

ほとんどの場合、答えはnoである必要があります。代わりに Pure DI を使用してください。あなたの答えはすべて、PureDIで答えるのは簡単です。

Unity

Unityを使用する必要がある場合、おそらく次のことが役に立ちます。私は2011年からUnityを使用していないので、それ以降は状況が変わった可能性がありますが、 私の本 のセクション14.3.3で問題を調べると、次のような方法でうまくいく可能性があります。

container.RegisterType<ICustomerService, CustomerService>("custSvc");
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
    new InjectionConstructor(
        new ResolvedParameter<ICustomerService>("custSvc")));

または、これを行うこともできます。

container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
    new InjectionConstructor(
        new ResolvedParameter<CustomerService>()));

この代替手段は、名前付きサービスに依存しないため、保守が容易ですが、CustomerServiceインターフェイスを介してICustomerServiceを解決できないという(潜在的な)欠点があります。とにかくそうするべきではないので、問題になることはないはずなので、これはおそらくより良い代替手段です。

19
Mark Seemann

質問:重要な情報が不足していると思います。循環依存が原因でStackoverflowExceptionsが発生します。依存性の注入/制御の反転の原則と規則に従いながら、デコレータクラスを正しく実装するにはどうすればよいですか?

すでに指摘したように、これを行うための最良の方法は、次の構成を使用することです。

container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
    new InjectionConstructor(new ResolvedParameter<CustomerService>()));

これにより、パラメータをタイプごとに解決する方法を指定できます。名前で行うこともできますが、タイプ別はよりクリーンな実装であり、文字列の変更やタイプミスが検出されないため、コンパイル時のチェックが向上します。 このコード部分とMark Seemannによって提供されたコードのわずかな違いは、InjectionConstructorのスペルの修正であることに注意してください。マーク・シーマンがまだ説明していないことを追加するものが他にないので、この部分についてはこれ以上詳しく説明しません。


2番目の質問:特定の状況でのみロギングデコレータを適用したいと思った場合はどうなりますか?したがって、CustomerServiceLoggingDecorator依存関係が必要なMyController1があり、MyController2には通常のCustomerServiceのみが必要な場合、2つの個別の登録を作成するにはどうすればよいですか?

これは、Fluent表記OR)を使用して、上記で指定した方法を使用して、依存関係のオーバーライドで名前付き依存関係を使用して行うことができます。

流暢

これにより、コントローラーがコンテナーに登録され、コンストラクターでそのタイプのオーバーロードが指定されます。私は2番目よりもこのアプローチを好みますが、タイプを指定する場所によって異なります。

container.RegisterType<MyController2>(
    new InjectionConstructor(new ResolvedParameter<CustomerService>()));

名前付き依存関係

これはまったく同じ方法で行い、両方をそのように登録します。

container.RegisterType<ICustomerService, CustomerService>("plainService");

container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
    new InjectionConstructor(new ResolvedParameter<CustomerService>()));

ここでの違いは、同じインターフェイスを使用して解決できる他のタイプの代わりに、名前付きの依存関係を使用することです。これは、Unityによって解決が行われるたびに、インターフェイスを1つの具象型に正確に解決する必要があるため、同じインターフェイスに複数の名前のない登録済みタイプを登録することはできません。これで、属性を使用してコントローラーコンストラクターでオーバーライドを指定できます。私の例は、MyController2という名前のコントローラーの場合で、上記の登録で指定された名前のDependency属性を追加しました。したがって、このコンストラクターでは、デフォルトのCustomerService型の代わりにCustomerServiceLoggingDecorator型が挿入されます。 MyController1は、タイプICustomerServiceであるCustomerServiceLoggingDecoratorのデフォルトの名前のない登録を引き続き使用します。

public MyController2([Dependency("plainService")]ICustomerService service) 

public MyController1(ICustomerService service) 

コンテナ自体のタイプを手動で解決するときにこれを行う方法もあります。 オーバーライドを使用したオブジェクトの解決 を参照してください。ここでの問題は、これを行うためにコンテナ自体にアクセスする必要があることですが、これはお勧めできません。別の方法として、コンテナーのラッパーを作成し、それをコントローラー(または他のタイプ)に挿入して、オーバーライドを使用してその方法でタイプを取得することもできます。繰り返しますが、これは少し厄介になるので、可能であれば避けたいと思います。

2
Igor

Markの2番目の回答に基づいて、CustomerServiceInjectionFactoryに登録し、次のようなインターフェイスなしでサービスタイプにのみ登録することを検討します。

_containter.RegisterType<CustomerService>(new InjectionFactory(
    container => new CustomerService(containter.Resolve<IGenericRepository<Customer>>())));
_

これにより、Markの回答のように、次のようにロギングオブジェクトを登録できるようになります。

_containter.RegisterType<ICutomerService, CutomerServiceLoggingDecorator>(new InjectionConstructor(
    new ResolvedParameter<CustomerService>()));
_

これは基本的に、オブジェクトを_Lazy<IService>_に依存させたくないため、何かを遅延ロードする必要がある場合に使用する手法と同じです。プロキシでラップすることにより、IServiceのみを挿入できますが、遅延して解決されます。プロキシを介して。

これにより、magicの代わりにオブジェクトのCustomerServiceを解決するだけで、ICustomerService文字列を要求する代わりに、ロギングオブジェクトまたは通常のオブジェクトのどちらを挿入するかを選択できます。

ロギングの場合CustomerService

container.Resolve<ICustomerService>()

または、ログに記録されないCustomerServiceの場合:

container.Resolve<CustomerService>()

0
Stephen Ross