C# 在不显式指定InjectionConstructor中的每个参数的情况下,如何将Decorator模式与Unity结合使用
David Haydn的这篇有用的文章(编辑:scam链接被删除,它可能已经被删除)展示了如何使用C# 在不显式指定InjectionConstructor中的每个参数的情况下,如何将Decorator模式与Unity结合使用,c#,unity-container,decorator,C#,Unity Container,Decorator,David Haydn的这篇有用的文章(编辑:scam链接被删除,它可能已经被删除)展示了如何使用InjectionConstructor类来帮助您使用Unity的decorator模式建立一个链。但是,如果decorator链中的项在其构造函数中有其他参数,InjectionConstructor必须显式声明它们中的每一个(否则Unity会抱怨它找不到正确的构造函数)。这意味着您不能简单地向decorator链中的项添加新的构造函数参数,而不更新Unity配置代码 这里有一些示例代码来解释我的
InjectionConstructor
类来帮助您使用Unity的decorator模式建立一个链。但是,如果decorator链中的项在其构造函数中有其他参数,InjectionConstructor
必须显式声明它们中的每一个(否则Unity会抱怨它找不到正确的构造函数)。这意味着您不能简单地向decorator链中的项添加新的构造函数参数,而不更新Unity配置代码
这里有一些示例代码来解释我的意思。ProductRepository
类首先由CachingProductRepository
包装,然后由loggingProductRepository
包装。CachingProductRepository和LoggingProductRepository除了在其构造函数中使用IPProductRepository外,还需要来自容器的其他接口
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);
}
}
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>());
这里有一个(通过的)单元测试。请参阅注释,了解我想要消除的多余配置位:
[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>());
}
[测试]
公共无效ResolveWithDecorators()
{
UnityContainer c=新的UnityContainer();
c、 RegisterInstance(新的Mock().Object);
c、 RegisterInstance(新的Mock().Object);
c、 RegisterInstance(新的Mock().Object);
c、 RegisterType(“ProductRepository”);
//不希望每次CachingProductRepository构造函数获取另一个参数时都必须更新此行
var dependOnProductRepository=new InjectionConstructor(new ResolvedParameter(“ProductRepository”),new ResolvedParameter();
c、 RegisterType(“CachingProductRepository”,dependOnProductRepository);
//不希望每次LoggingProductRepository构造函数更改时都必须更新此行
var dependchingproductrepository=new InjectionConstructor(new ResolvedParameter(“CachingProductRepository”),new ResolvedParameter();
c、 RegisterType(DependochingProductRepository);
Assert.IsInstanceOf(c.Resolve());
}
当我在等待答案时,我想出了一个相当粗糙的解决方法。我已经在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;
}
}
}
静态类decororUnityExtensions
{
公共静态无效注册表decoratorChain(此IUnityContainer容器,类型[]decoratorChain)
{
类型parent=null;
字符串parentName=null;
foreach(装饰链中的t型)
{
字符串namedInstance=Guid.NewGuid().ToString();
如果(父项==null)
{
//顶层,只需做一个普通的寄存器类型
容器注册表类型(类型(T),T,名称状态);
}
其他的
{
//在这里可能会更聪明。只需要第一个构造器
var constructor=t.GetConstructors()[0];
var resolvedParameters=新列表();
foreach(constructor.GetParameters()中的var constructorParam)
{
if(constructorParam.ParameterType==typeof(T))
{
添加(新的ResolvedParameter(parentName));
}
其他的
{
添加(新的ResolvedParameter(constructorParam.ParameterType));
}
}
if(t==decoratorChain.Last())
{
//不是命名实例
RegisterType(typeof(T),T,newinjectionconstructor(resolvedParameters.ToArray());
}
其他的
{
RegisterType(typeof(T),T,namedInstance,newinjectionconstructor(resolvedParameters.ToArray());
}
}
父代=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>());
}
[测试]
使用Decorators 2()解决公共无效问题
{
UnityContainer c=新的UnityContainer();
c、 RegisterInstance(新的Mock().Object);
c、 RegisterInstance(新的Mock().Object);
c、 RegisterInstance(新的Mock().Object);
c、 RegisterDecoratorChain(新类型[]{typeof(ProductRepository)、typeof(CachingProductRepository)、typeof(LoggingProductRepository)});
Assert.IsInstanceOf(c.Resolve());
}
我仍然有兴趣知道是否有一个更优雅的解决方案,使用Unity另一种方法,感谢@DarkSquirrel42的建议,就是使用
注入工厂
。缺点是每次在链中添加新的构造函数参数时,代码仍然需要更新。其优点是代码更容易理解,并且只需在容器中注册一次
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);
}
}
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>());
Func createChain=container=>
新LoggingProductRepository(
新CachingProductRepository(
container.Resolve(),
container.Resolve()),
container.Resolve());
c、 RegisterType(新注入工厂(createChain));
Assert.IsInstanceOf(c.Resolve());
请参见关于实现装饰器容器扩展的说明。如果构造函数签名发生更改,则无需修改配置。另一种解决方案是向代码库中添加类型参数,以帮助Unity解析修饰类型。幸运的是,Unity完全能够自行解析类型参数及其依赖关系,因此在de时我们不必关心构造函数参数
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>();
container.RegisterType<ILogger, DecoratorLogger>(
new InjectionConstructor(
new ResolvedParameter<Log4NetLogger>()));
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));
}
}