依赖注入组合根和装饰器模式
Dependency Injection composition root and decorator pattern
我在使用依赖项注入时在装饰器模式的实现中遇到了 WhosebugException
。我认为这是因为我是 "missing" 我对 DI/IoC 的理解。
例如,我目前有 CustomerService
和 CustomerServiceLoggingDecorator
。 classes 都实现了 ICustomerService
,装饰器 class 所做的只是使用注入的 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>();
}
所以现在我不确定 "proper" 实际创建具有依赖项注入的装饰器的方法。我的装饰器基于 Mark Seemann 的回答 here。在他的示例中,他正在更新几个传递到 class 的对象。这就是我的 it "works"* 片段的工作原理。但是,我想我错过了一个基本的步骤。
为什么要手动创建这样的新对象?这是否否定了让容器为我解析的意义?或者我应该在这个方法中执行 contain.Resolve()
(服务定位器),让所有依赖项仍然注入?
我稍微熟悉 "composition root" 概念,即您应该将这些依赖项连接到一个且仅一个位置,然后向下级联到应用程序的较低级别。那么 Unity.Mvc
生成的 RegisterTypes()
是 ASP.NET MVC 应用程序的组合根吗?如果是这样,直接在此处更新对象实际上是正确的吗?
我的印象是,通常使用 Unity 您需要自己创建合成根,但是,Unity.Mvc
是一个例外,它创建了自己的合成根,因为它似乎能够将依赖项注入到控制器中,这些控制器在构造函数中具有诸如 ICustomerService
的接口,而无需我编写代码来实现它。
问题:我想我遗漏了一条关键信息,由于循环依赖,这导致我 WhosebugExceptions
。如何在仍然遵循控制原则和约定的依赖性 injection/inversion 的同时正确实施我的装饰器 class?
第二个问题:如果我决定只在某些情况下应用日志装饰器呢?因此,如果我有 MyController1
,我希望有一个 CustomerServiceLoggingDecorator
依赖项,但 MyController2
只需要一个正常的 CustomerService
,我该如何创建两个单独的注册?因为如果我这样做:
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();
container.RegisterType<ICustomerService, CustomerService>();
然后一个将被覆盖,这意味着两个控制器都将注入装饰器或注入普通服务。我如何允许两者兼顾?
编辑:这不是一个重复的问题,因为我在循环依赖方面遇到问题,并且对此缺乏正确的 DI 方法的理解。我的问题适用于整个概念,而不仅仅是像链接问题那样的装饰器模式。
前言
每当您在使用 DI 容器(Unity 或其他)时遇到问题,问问自己:is using a DI Container worth the effort?
在大多数情况下,答案应该是否。请改用 Pure DI。用 Pure DI 回答你所有的答案都是微不足道的。
团结
如果您必须使用Unity,以下内容或许会对您有所帮助。我自 2011 年以来就没有使用过 Unity,所以从那以后情况可能发生了变化,但是在 my book 的第 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>()));
此替代方案更易于维护,因为它不依赖于命名服务,但具有无法通过 ICustomerService
接口解析 CustomerService
的(潜在)缺点。无论如何你可能不应该这样做,所以这应该不是问题,所以这可能是一个更好的选择。
Question: I believe I'm missing a key piece of information, which is leading me to WhosebugExceptions due to circular dependencies. How do I correctly implement my decorator class while still following dependency injection/inversion of control principles and conventions?
正如已经指出的那样,最好的方法是使用以下结构。
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(new ResolvedParameter<CustomerService>()));
这允许您指定如何按类型解析参数。您也可以按名称执行此操作,但按类型执行更清晰,并且允许在编译时进行更好的检查,因为不会捕获字符串中的更改或错误键入。 请注意,此代码部分与 Mark Seemann 提供的代码之间的唯一细微差别是 InjectionConstructor
的拼写更正。我不会再详细说明这部分,因为没有其他要补充的,Mark Seemann 还没有解释。
Second question: What about if I decided I only wanted to apply the logging decorator in certain circumstances? So if I had MyController1 that I wished to have a CustomerServiceLoggingDecorator dependency, but MyController2 only needs a normal CustomerService, how do I create two separate registrations?
您可以使用上面指定的方式使用 Fluent 表示法或使用具有依赖项覆盖的命名依赖项来执行此操作。
流利
这会向容器注册控制器并在构造函数中为该类型指定重载。与第二种方法相比,我更喜欢这种方法,但这仅取决于您要在何处指定类型。
container.RegisterType<MyController2>(
new InjectionConstructor(new ResolvedParameter<CustomerService>()));
命名依赖项
你以完全相同的方式执行此操作,你可以像这样注册它们。
container.RegisterType<ICustomerService, CustomerService>("plainService");
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(new ResolvedParameter<CustomerService>()));
此处的不同之处在于,您对可以使用同一接口解析的其他类型使用命名依赖项。这是因为每次 Unity 完成解析时,接口都需要解析为一个具体类型,因此您不能将多个未命名的注册类型注册到同一接口。现在您可以使用属性在控制器构造函数中指定覆盖。我的示例是针对名为 MyController2
的控制器,我添加了 Dependency
属性,其名称也在上面的注册中指定。因此,对于此构造函数,将注入 CustomerService
类型而不是默认的 CustomerServiceLoggingDecorator
类型。 MyController1
仍将使用 ICustomerService
的默认未命名注册,类型为 CustomerServiceLoggingDecorator
。
public MyController2([Dependency("plainService")]ICustomerService service)
public MyController1(ICustomerService service)
当您手动解析容器本身的类型时,也有一些方法可以做到这一点,请参阅 Resolving Objects by Using Overrides。这里的问题是您需要访问容器本身才能执行此操作,这是不推荐的。作为替代方案,您可以在容器周围创建一个包装器,然后将其注入到控制器(或其他类型)中,然后通过覆盖方式检索一个类型。同样,这有点混乱,如果可能的话我会避免它。
基于 Mark 的第二个答案,我希望用 InjectionFactory
注册 CustomerService
并且只使用没有它的接口的服务类型注册它,例如:
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
但通过代理延迟解决。
这也将允许您通过简单地为您的对象解析 CustomerService
而不是 ICustomerService
.
对于日志记录 CustomerService
:
container.Resolve<ICustomerService>()
或者对于非日志记录 CustomerService
:
container.Resolve<CustomerService>()
我在使用依赖项注入时在装饰器模式的实现中遇到了 WhosebugException
。我认为这是因为我是 "missing" 我对 DI/IoC 的理解。
例如,我目前有 CustomerService
和 CustomerServiceLoggingDecorator
。 classes 都实现了 ICustomerService
,装饰器 class 所做的只是使用注入的 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>();
}
所以现在我不确定 "proper" 实际创建具有依赖项注入的装饰器的方法。我的装饰器基于 Mark Seemann 的回答 here。在他的示例中,他正在更新几个传递到 class 的对象。这就是我的 it "works"* 片段的工作原理。但是,我想我错过了一个基本的步骤。
为什么要手动创建这样的新对象?这是否否定了让容器为我解析的意义?或者我应该在这个方法中执行 contain.Resolve()
(服务定位器),让所有依赖项仍然注入?
我稍微熟悉 "composition root" 概念,即您应该将这些依赖项连接到一个且仅一个位置,然后向下级联到应用程序的较低级别。那么 Unity.Mvc
生成的 RegisterTypes()
是 ASP.NET MVC 应用程序的组合根吗?如果是这样,直接在此处更新对象实际上是正确的吗?
我的印象是,通常使用 Unity 您需要自己创建合成根,但是,Unity.Mvc
是一个例外,它创建了自己的合成根,因为它似乎能够将依赖项注入到控制器中,这些控制器在构造函数中具有诸如 ICustomerService
的接口,而无需我编写代码来实现它。
问题:我想我遗漏了一条关键信息,由于循环依赖,这导致我 WhosebugExceptions
。如何在仍然遵循控制原则和约定的依赖性 injection/inversion 的同时正确实施我的装饰器 class?
第二个问题:如果我决定只在某些情况下应用日志装饰器呢?因此,如果我有 MyController1
,我希望有一个 CustomerServiceLoggingDecorator
依赖项,但 MyController2
只需要一个正常的 CustomerService
,我该如何创建两个单独的注册?因为如果我这样做:
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();
container.RegisterType<ICustomerService, CustomerService>();
然后一个将被覆盖,这意味着两个控制器都将注入装饰器或注入普通服务。我如何允许两者兼顾?
编辑:这不是一个重复的问题,因为我在循环依赖方面遇到问题,并且对此缺乏正确的 DI 方法的理解。我的问题适用于整个概念,而不仅仅是像链接问题那样的装饰器模式。
前言
每当您在使用 DI 容器(Unity 或其他)时遇到问题,问问自己:is using a DI Container worth the effort?
在大多数情况下,答案应该是否。请改用 Pure DI。用 Pure DI 回答你所有的答案都是微不足道的。
团结
如果您必须使用Unity,以下内容或许会对您有所帮助。我自 2011 年以来就没有使用过 Unity,所以从那以后情况可能发生了变化,但是在 my book 的第 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>()));
此替代方案更易于维护,因为它不依赖于命名服务,但具有无法通过 ICustomerService
接口解析 CustomerService
的(潜在)缺点。无论如何你可能不应该这样做,所以这应该不是问题,所以这可能是一个更好的选择。
Question: I believe I'm missing a key piece of information, which is leading me to WhosebugExceptions due to circular dependencies. How do I correctly implement my decorator class while still following dependency injection/inversion of control principles and conventions?
正如已经指出的那样,最好的方法是使用以下结构。
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(new ResolvedParameter<CustomerService>()));
这允许您指定如何按类型解析参数。您也可以按名称执行此操作,但按类型执行更清晰,并且允许在编译时进行更好的检查,因为不会捕获字符串中的更改或错误键入。 请注意,此代码部分与 Mark Seemann 提供的代码之间的唯一细微差别是 InjectionConstructor
的拼写更正。我不会再详细说明这部分,因为没有其他要补充的,Mark Seemann 还没有解释。
Second question: What about if I decided I only wanted to apply the logging decorator in certain circumstances? So if I had MyController1 that I wished to have a CustomerServiceLoggingDecorator dependency, but MyController2 only needs a normal CustomerService, how do I create two separate registrations?
您可以使用上面指定的方式使用 Fluent 表示法或使用具有依赖项覆盖的命名依赖项来执行此操作。
流利
这会向容器注册控制器并在构造函数中为该类型指定重载。与第二种方法相比,我更喜欢这种方法,但这仅取决于您要在何处指定类型。
container.RegisterType<MyController2>(
new InjectionConstructor(new ResolvedParameter<CustomerService>()));
命名依赖项
你以完全相同的方式执行此操作,你可以像这样注册它们。
container.RegisterType<ICustomerService, CustomerService>("plainService");
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
new InjectionConstructor(new ResolvedParameter<CustomerService>()));
此处的不同之处在于,您对可以使用同一接口解析的其他类型使用命名依赖项。这是因为每次 Unity 完成解析时,接口都需要解析为一个具体类型,因此您不能将多个未命名的注册类型注册到同一接口。现在您可以使用属性在控制器构造函数中指定覆盖。我的示例是针对名为 MyController2
的控制器,我添加了 Dependency
属性,其名称也在上面的注册中指定。因此,对于此构造函数,将注入 CustomerService
类型而不是默认的 CustomerServiceLoggingDecorator
类型。 MyController1
仍将使用 ICustomerService
的默认未命名注册,类型为 CustomerServiceLoggingDecorator
。
public MyController2([Dependency("plainService")]ICustomerService service)
public MyController1(ICustomerService service)
当您手动解析容器本身的类型时,也有一些方法可以做到这一点,请参阅 Resolving Objects by Using Overrides。这里的问题是您需要访问容器本身才能执行此操作,这是不推荐的。作为替代方案,您可以在容器周围创建一个包装器,然后将其注入到控制器(或其他类型)中,然后通过覆盖方式检索一个类型。同样,这有点混乱,如果可能的话我会避免它。
基于 Mark 的第二个答案,我希望用 InjectionFactory
注册 CustomerService
并且只使用没有它的接口的服务类型注册它,例如:
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
但通过代理延迟解决。
这也将允许您通过简单地为您的对象解析 CustomerService
而不是 ICustomerService
.
对于日志记录 CustomerService
:
container.Resolve<ICustomerService>()
或者对于非日志记录 CustomerService
:
container.Resolve<CustomerService>()