问题描述
|
大卫·海顿(David Haydn)的这篇有用的文章(编辑:骗局链接已删除,可能是本文)显示了如何使用
InjectionConstructor
类来帮助您使用带有Unity的装饰器模式来设置链。但是,如果装饰器链中的项在其构造函数中具有其他参数,则ѭ0必须显式声明每个参数(否则Unity会抱怨找不到合适的构造函数)。这意味着您不能在不更新Unity配置代码的情况下简单地向装饰器链中的项目添加新的构造函数参数。
这是一些示例代码来解释我的意思。 ProductRepository
类首先被CachingProductRepository
包裹,然后被LoggingProductRepostiory
包裹。除了在其构造函数中使用IProductRepository之外,CachingProductRepository和LoggingProductRepository都需要容器中的其他接口。
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,LifetimeManager lifetimeManager,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,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>()));
,在等待有关此问题的答案时,我想出了一个相当棘手的解决方法。我在ѭ17上创建了一个扩展方法,该方法使我可以使用反射来注册装饰器链以创建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),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),new InjectionConstructor(resolvedParameters.ToArray()));
}
else
{
container.RegisterType(typeof(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是否有更优雅的解决方案