如何在不明确指定InjectionConstructor中的每个参数的情况下将Decorator模式与Unity一起使用

Mar*_*ath 36 c# decorator unity-container

这篇来自David Haydn的有用文章(编辑:删除了骗局链接,可能是本文)展示了如何使用InjectionConstructor该类来帮助您使用Unity的装饰器模式来设置链.但是,如果装饰器链中的项目在其构造函数中具有其他参数,则InjectionConstructor必须显式声明它们中的每一个(或者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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

这是一个(通过)单元测试.请参阅有关剩余配置位的注释我想删除以下内容:

    [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>());
    }
Run Code Online (Sandbox Code Playgroud)

Mar*_*ath 28

由于@ 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>());
Run Code Online (Sandbox Code Playgroud)

  • 这也有一个好处,即对构造函数的更改将生成编译时而不是运行时错误. (7认同)

Ken*_*nic 12

请参阅这篇文章上实现装饰容器延长.如果您的构造函数签名发生更改,这将使您无需修改​​配置即可到达目标位置.


Goo*_*ide 6

另一种解决方案是将类型参数添加到代码库中,以帮助Unity解析您的装饰类型.幸运的是,Unity完全能够自己解析类型参数及其依赖关系,因此在定义装饰器链时我们不必关心构造函数参数.

注册情况如下:

unityContainer.RegisterType<IService, Logged<Profiled<Service>>>();
Run Code Online (Sandbox Code Playgroud)

这是一个基本的示例实现.注意模板化的装饰器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();
    }
}
Run Code Online (Sandbox Code Playgroud)

缺点

  • 错误的注册uC.RegisterType<IService, Logged<IService>>();会导致无限递归,堆栈溢出您的应用程序.这可能是插件架构中的漏洞.
  • 它在某种程度上使你的代码库变得丑陋.如果您放弃Unity并切换到不同的DI框架,那么这些模板参数对任何人都没有任何意义.


gar*_*ryp 5

我为此提出了一个相当粗糙的扩展方法,当我运行它时,它的行为符合预期:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

在您的设置中:

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>();
Run Code Online (Sandbox Code Playgroud)