依存性注入を使用すると、デコレータパターンの実装でStackoverflowException
が発生します。 DI/IoCの理解から何かが「欠けている」からだと思います。
たとえば、私は現在CustomerService
とCustomerServiceLoggingDecorator
を持っています。どちらのクラスもICustomerService
を実装し、デコレータクラスは注入されたICustomerService
を使用するだけですが、CustomerService
のコードに影響を与えずにロギングを使用できるように、いくつかの単純なNLogロギングを追加します。また、単一責任の原則に違反していません。
ただし、ここでの問題は、CustomerServiceLoggingDecorator
がICustomerService
を実装し、動作するために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アプローチの理解が不足しているため、これは重複する質問ではありません。私の質問は、リンクされた質問のようなデコレータパターンだけでなく、概念全体に当てはまります。
DIコンテナー(Unityまたはその他)で問題が発生した場合は、次のことを自問してください。 努力する価値のあるDIコンテナーを使用していますか?
ほとんどの場合、答えはnoである必要があります。代わりに Pure DI を使用してください。あなたの答えはすべて、PureDIで答えるのは簡単です。
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
を解決できないという(潜在的な)欠点があります。とにかくそうするべきではないので、問題になることはないはずなので、これはおそらくより良い代替手段です。
質問:重要な情報が不足していると思います。循環依存が原因で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)
コンテナ自体のタイプを手動で解決するときにこれを行う方法もあります。 オーバーライドを使用したオブジェクトの解決 を参照してください。ここでの問題は、これを行うためにコンテナ自体にアクセスする必要があることですが、これはお勧めできません。別の方法として、コンテナーのラッパーを作成し、それをコントローラー(または他のタイプ)に挿入して、オーバーライドを使用してその方法でタイプを取得することもできます。繰り返しますが、これは少し厄介になるので、可能であれば避けたいと思います。
Markの2番目の回答に基づいて、CustomerService
をInjectionFactory
に登録し、次のようなインターフェイスなしでサービスタイプにのみ登録することを検討します。
_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>()