依存性注入を使用するときに、デコレータ パターンの実装で 'sが発生しますStackoverflowException
。これは、DI/IoC の理解が「欠けている」ためだと思います。
たとえば、現在CustomerService
と がありますCustomerServiceLoggingDecorator
。どちらのクラスも を実装しておりICustomerService
、デコレータ クラスが行うことは、注入された を使用することだけですICustomerService
が、単純な NLog ログ記録を追加することで、 のコードに影響を与えずにログ記録を使用でき、CustomerService
単一責任の原則に違反することもありません。
しかし、ここでの問題は、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>();
}
それで、依存性注入でデコレータを作成する「適切な」方法がわかりません。私はMark Seemannの回答に基づいてデコレータを作成しました。ここ. 彼の例では、クラスに渡されるいくつかのオブジェクトを新しく作成しています。これが私の it "works"* スニペットの動作方法です。ただし、基本的な手順を見逃していると思います。
なぜこのように手動で新しいオブジェクトを作成するのでしょうか? これでは、コンテナーに解決を任せる意味がなくなってしまうのではないですか? それとも、代わりにcontain.Resolve()
この 1 つのメソッド内で (サービス ロケーター) を実行して、引き続きすべての依存関係を注入するべきでしょうか?
私は「コンポジション ルート」という概念に少し精通しています。これは、これらの依存関係を 1 か所にのみ接続し、それをアプリケーションの下位レベルにカスケードするものです。では、Unity.Mvc
生成されたRegisterTypes()
ものは ASP.NET MVC アプリケーションのコンポジション ルートですか? そうであれば、ここでオブジェクトを直接新規作成するのは正しいのでしょうか?
通常、Unity ではコンポジション ルートを自分で作成する必要があるという印象を持っていましたが、これは例外で、独自のコンポジション ルートが作成されます。これは、コードを記述しなくても、コンストラクターUnity.Mvc
などのインターフェイスを持つコントローラーに依存関係を挿入できるようです。ICustomerService
質問: 循環依存関係が原因で、重要な情報が欠落していると思いますStackoverflowExceptions
。依存性注入/制御の反転の原則と規則に従いながら、デコレータ クラスを正しく実装するにはどうすればよいでしょうか?
2番目の質問: 特定の状況でのみロギング デコレータを適用したいと決めた場合はどうでしょうか。依存関係MyController1
を持たせたいがCustomerServiceLoggingDecorator
、MyController2
通常の のみが必要な場合はCustomerService
、2 つの別々の登録を作成するにはどうすればよいでしょうか。なぜなら、次のようにすると:
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();
container.RegisterType<ICustomerService, CustomerService>();
そうすると、一方が上書きされ、両方のコントローラーにデコレーターが注入されるか、通常のサービスが注入されることになります。両方を許可するにはどうすればよいでしょうか?
編集: これは重複した質問ではありません。循環依存関係に問題があり、これに対する正しい DI アプローチを理解していないためです。私の質問は、リンクされた質問のようにデコレータ パターンだけでなく、概念全体に当てはまります。
ベストアンサー1
前文
DI コンテナー (Unity など) で問題が発生した場合は、次のことを自問してください。DI コンテナを使用する価値はあるでしょうか?
ほとんどの場合、答えはいいえ。 使用ピュアDI代わりに、すべての回答は Pure DI で簡単に答えられます。
団結
もし、あんたがしなければならないUnityを使っているなら、以下の記事が役に立つかもしれません。私は2011年以降Unityを使っていないので、それ以降は状況が変わっているかもしれませんが、私の本次のようなものが役に立つかもしれません:
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
。いずれにしても、おそらくそうすべきではないので、問題にはならないはずです。したがって、これはおそらくより優れた代替案です。