如何在Unity中使用装饰器模式而不在InjectionConstructor中显式指定每个参数

如何解决如何在Unity中使用装饰器模式而不在InjectionConstructor中显式指定每个参数

| 大卫·海顿(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是否有更优雅的解决方案     

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?