David Haydon的这篇有用的文章(编辑:删除了骗局链接,可能是这篇文章)展示了如何使用InjectionConstructor
类来帮助您使用Unity的装饰器模式建立链。但是,如果装饰器链中的项在其构造函数中有其他参数,则InjectionConstructor
必须显式声明其中的每一个(否则Unity将抱怨找不到正确的构造函数)。这意味着,在不更新Unity配置代码的情况下,您不能简单地向decorator链中的项添加新的构造函数参数。
下面是一些示例代码来解释我的意思。ProductRepository
类首先由CachingProductRepository
封装,然后由LoggingProductRepostiory
封装。CachingProductRepository和LoggingProductRepository除了在其构造函数中使用IProductRepository外,还需要容器中的其他接口。
public class Product
{
public int Id;
public string Name;
}
public interface IDatabaseConnection { }
public interface ICacheProvider
{
object GetFromCache(string key);
void AddToCache(string key, object value);
}
public interface ILogger
{
void Log(string message, params object[] args);
}
public interface IProductRepository
{
Product GetById(int id);
}
class ProductRepository : IProductRepository
{
public ProductRepository(IDatabaseConnection db)
{
}
public Product GetById(int id)
{
return new Product() { Id = id, Name = "Foo " + id.ToString() };
}
}
class CachingProductRepository : IProductRepository
{
IProductRepository repository;
ICacheProvider cacheProvider;
public CachingProductRepository(IProductRepository repository, ICacheProvider cp)
{
this.repository = repository;
this.cacheProvider = cp;
}
public Product GetById(int id)
{
string key = "Product " + id.ToString();
Product p = (Product)cacheProvider.GetFromCache(key);
if (p == null)
{
p = repository.GetById(id);
cacheProvider.AddToCache(key, p);
}
return p;
}
}
class LoggingProductRepository : IProductRepository
{
private IProductRepository repository;
private ILogger logger;
public LoggingProductRepository(IProductRepository repository, ILogger logger)
{
this.repository = repository;
this.logger = logger;
}
public Product GetById(int id)
{
logger.Log("Requesting product {0}", id);
return repository.GetById(id);
}
}
这是一个(通过的)单元测试。查看剩余配置的注释,我想删除对的需求
[Test]
public void ResolveWithDecorators()
{
UnityContainer c = new UnityContainer();
c.RegisterInstance<IDatabaseConnection>(new Mock<IDatabaseConnection>().Object);
c.RegisterInstance<ILogger>(new Mock<ILogger>().Object);
c.RegisterInstance<ICacheProvider>(new Mock<ICacheProvider>().Object);
c.RegisterType<IProductRepository, ProductRepository>("ProductRepository");
// don't want to have to update this line every time the CachingProductRepository constructor gets another parameter
var dependOnProductRepository = new InjectionConstructor(new ResolvedParameter<IProductRepository>("ProductRepository"), new ResolvedParameter<ICacheProvider>());
c.RegisterType<IProductRepository, CachingProductRepository>("CachingProductRepository", dependOnProductRepository);
// don't want to have to update this line every time the LoggingProductRepository constructor changes
var dependOnCachingProductRepository = new InjectionConstructor(new ResolvedParameter<IProductRepository>("CachingProductRepository"), new ResolvedParameter<ILogger>());
c.RegisterType<IProductRepository, LoggingProductRepository>(dependOnCachingProductRepository);
Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());
}
根据@DarkSquirrel42的建议,另一种方法是使用InjectionFactory
。缺点是,每次向链中的某个对象添加新的构造函数参数时,代码仍然需要更新。其优点是更容易理解代码,并且只需在容器中进行一次注册。
Func<IUnityContainer,object> createChain = container =>
new LoggingProductRepository(
new CachingProductRepository(
container.Resolve<ProductRepository>(),
container.Resolve<ICacheProvider>()),
container.Resolve<ILogger>());
c.RegisterType<IProductRepository>(new InjectionFactory(createChain));
Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());
请参阅本文中关于实现装饰器容器扩展的内容。如果构造函数签名发生更改,就不需要修改配置,这将使您达到您想要的位置。
另一个解决方案涉及将类型参数添加到代码库中,以帮助Unity解析修饰的类型。幸运的是,Unity完全能够自行解析类型参数及其依赖项,因此在定义装饰器链时,我们不必关心构造函数参数。
注册情况如下:
unityContainer.RegisterType<IService, Logged<Profiled<Service>>>();
下面是一个基本的示例实现。注意模板化的装饰器Logged<TService>
和Profiled<TService>
。下面是我注意到的一些缺点。
public interface IService { void Do(); }
public class Service : IService { public void Do() { } }
public class Logged<TService> : IService where TService : IService
{
private TService decoratee;
private ILogger logger;
public Logged(ILogger logger, TService decoratee) {
this.decoratee = decoratee;
this.logger = logger;
}
public void Do() {
logger.Debug("Do()");
decoratee.Do();
}
}
public class Profiled<TService> : IService where TService : IService
{
private TService decoratee;
private IProfiler profiler;
public Profiled(IProfiler profiler, TService decoratee) {
this.decoratee = decoratee;
this.profiler = profiler;
}
public void Do() {
profiler.Start();
decoratee.Do();
profiler.Stop();
}
}
缺点
- 像
uC.RegisterType<IService, Logged<IService>>();
这样的错误注册将导致堆栈溢出应用程序的无限递归。这可能是插件体系结构中的一个漏洞 - 它在某种程度上丑化了你的代码库。如果你放弃了Unity,转而使用不同的DI框架,那么这些模板参数对任何人来说都没有意义了
我为此开发了一个相当粗糙的扩展方法,当我运行它时,它的行为正如预期的那样:
public static class UnityExtensions
{
public static IUnityContainer Decorate<TInterface, TDecorator>(this IUnityContainer container, params InjectionMember[] injectionMembers)
where TDecorator : class, TInterface
{
return Decorate<TInterface, TDecorator>(container, null, injectionMembers);
}
public static IUnityContainer Decorate<TInterface, TDecorator>(this IUnityContainer container, LifetimeManager lifetimeManager, params InjectionMember[] injectionMembers)
where TDecorator : class, TInterface
{
string uniqueId = Guid.NewGuid().ToString();
var existingRegistration = container.Registrations.LastOrDefault(r => r.RegisteredType == typeof(TInterface));
if(existingRegistration == null)
{
throw new ArgumentException("No existing registration found for the type " + typeof(TInterface));
}
var existing = existingRegistration.MappedToType;
//1. Create a wrapper. This is the actual resolution that will be used
if (lifetimeManager != null)
{
container.RegisterType<TInterface, TDecorator>(uniqueId, lifetimeManager, injectionMembers);
}
else
{
container.RegisterType<TInterface, TDecorator>(uniqueId, injectionMembers);
}
//2. Unity comes here to resolve TInterface
container.RegisterType<TInterface, TDecorator>(new InjectionFactory((c, t, sName) =>
{
//3. We get the decorated class instance TBase
var baseObj = container.Resolve(existing);
//4. We reference the wrapper TDecorator injecting TBase as TInterface to prevent stack overflow
return c.Resolve<TDecorator>(uniqueId, new DependencyOverride<TInterface>(baseObj));
}));
return container;
}
}
在您的设置中:
container.RegisterType<IProductRepository, ProductRepository>();
// Wrap ProductRepository with CachingProductRepository,
// injecting ProductRepository into CachingProductRepository for
// IProductRepository
container.Decorate<IProductRepository, CachingProductRepository>();
// Wrap CachingProductRepository with LoggingProductRepository,
// injecting CachingProductRepository into LoggingProductRepository for
// IProductRepository
container.Decorate<IProductRepository, LoggingProductRepository>();
Mark Seeman在另一篇stackoverflow帖子中提到了最简洁的答案,效果很好。它是简洁,不要求我使用命名注册,也不建议我使用Unity扩展。考虑一个名为ILogger的接口,该接口有两个实现,即Log4NetLogger和一个称为DecoratorLogger的装饰器实现。您可以根据ILogger接口注册DecoratorLogger,如下所示:
container.RegisterType<ILogger, DecoratorLogger>(
new InjectionConstructor(
new ResolvedParameter<Log4NetLogger>()));
在等待答案时,我想出了一个相当巧妙的解决方法。我在IUnityContainer
上创建了一个扩展方法,允许我使用反射注册装饰器链来创建InjectionConstructor参数:
static class DecoratorUnityExtensions
{
public static void RegisterDecoratorChain<T>(this IUnityContainer container, Type[] decoratorChain)
{
Type parent = null;
string parentName = null;
foreach (Type t in decoratorChain)
{
string namedInstance = Guid.NewGuid().ToString();
if (parent == null)
{
// top level, just do an ordinary register type
container.RegisterType(typeof(T), t, namedInstance);
}
else
{
// could be cleverer here. Just take first constructor
var constructor = t.GetConstructors()[0];
var resolvedParameters = new List<ResolvedParameter>();
foreach (var constructorParam in constructor.GetParameters())
{
if (constructorParam.ParameterType == typeof(T))
{
resolvedParameters.Add(new ResolvedParameter<T>(parentName));
}
else
{
resolvedParameters.Add(new ResolvedParameter(constructorParam.ParameterType));
}
}
if (t == decoratorChain.Last())
{
// not a named instance
container.RegisterType(typeof(T), t, new InjectionConstructor(resolvedParameters.ToArray()));
}
else
{
container.RegisterType(typeof(T), t, namedInstance, new InjectionConstructor(resolvedParameters.ToArray()));
}
}
parent = t;
parentName = namedInstance;
}
}
}
这使我可以用更可读的语法配置我的容器:
[Test]
public void ResolveWithDecorators2()
{
UnityContainer c = new UnityContainer();
c.RegisterInstance<IDatabaseConnection>(new Mock<IDatabaseConnection>().Object);
c.RegisterInstance<ILogger>(new Mock<ILogger>().Object);
c.RegisterInstance<ICacheProvider>(new Mock<ICacheProvider>().Object);
c.RegisterDecoratorChain<IProductRepository>(new Type[] { typeof(ProductRepository), typeof(CachingProductRepository), typeof(LoggingProductRepository) });
Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());
}
我仍然很想知道Unity 是否有更优雅的解决方案
我知道这篇文章有点过时,但事实上,最新版本没有完全功能的Unity装饰器实现(有很多突破性的更改,请参阅Unity wiki)。
我接受了@garryp答案(在我看来,这是这里唯一正确的答案),并根据最新的Unity容器API更改进行了修改:
public static IContainerRegistry RegisterDecorator<TInterface, TDecorator>(this IContainerRegistry container, ITypeLifetimeManager lifetimeManager, Type[] additionalInterfaces, params InjectionMember[] injectionMembers)
where TDecorator : class, TInterface
{
var unityContainer = container.GetContainer();
var existingRegistration = unityContainer.Registrations.LastOrDefault(r => r.RegisteredType == typeof(TInterface));
if (existingRegistration == null)
{
throw new ArgumentException("No existing registration found for the type " + typeof(TInterface));
}
var existing = existingRegistration.MappedToType;
var uniqueId = Guid.NewGuid().ToString();
// 1. Create a wrapper. This is the actual resolution that will be used
if (lifetimeManager != null)
{
unityContainer.RegisterType<TDecorator>(uniqueId, lifetimeManager, injectionMembers);
}
else
{
unityContainer.RegisterType<TDecorator>(uniqueId, injectionMembers);
}
unityContainer.RegisterType<TInterface, TDecorator>();
if (additionalInterfaces != null)
{
foreach (var additionalInterface in additionalInterfaces)
{
unityContainer.RegisterType(additionalInterface, typeof(TDecorator));
}
}
unityContainer.RegisterFactory<TDecorator>(DecoratorFactory);
return container;
object DecoratorFactory(IUnityContainer c)
{
// 3. We get the decorated class instance TBase
var baseObj = c.Resolve(existing);
// 4. We reference the wrapper TDecorator injecting TBase as TInterface to prevent stack overflow
return c.Resolve<TDecorator>(uniqueId, new DependencyOverride<TInterface>(baseObj));
}
}
区别在于:
- 正在使用
IContainerRegistry
类型而不是IUnityContainer
——这是因为我在Unity容器上使用PRISM包装器 - 添加了
additionalInterfaces
可选参数,以便能够注册也实现其他接口的装饰器 - 修改逻辑,使其适合当前的Unity API实现