依赖注入组合的根和装饰器模式 [英] Dependency Injection composition root and decorator pattern

查看:98
本文介绍了依赖注入组合的根和装饰器模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在使用依赖注入时,我在装饰器模式的实现中得到了StackoverflowException.我认为这是因为我从DI/IoC的理解中缺失"了一些东西.

例如,我目前有CustomerServiceCustomerServiceLoggingDecorator.这两个类都实现了ICustomerService,并且装饰器类所做的全部是使用注入的ICustomerService,但添加了一些简单的NLog日志记录,以便我可以使用日志记录而不影响CustomerService中的代码,同时也不会违反单一职责原则./p>

但是这里的问题是,因为CustomerServiceLoggingDecorator实现了ICustomerService,并且还需要注入到其中的ICustomerService实现才能起作用,所以Unity会继续尝试将其解析回自身,从而导致无限循环,直到它会使堆栈溢出.

这些是我的服务

public interface ICustomerService
{
    IEnumerable<Customer> GetAllCustomers();
}

public class CustomerService : ICustomerService
{
    private readonly IGenericRepository<Customer> _customerRepository;

    public CustomerService(IGenericRepository<Customer> customerRepository)
    {
        if (customerRepository == null)
        {
            throw new ArgumentNullException(nameof(customerRepository));
        }

        _customerRepository = customerRepository;
    }

    public IEnumerable<Customer> GetAllCustomers()
    {
        return _customerRepository.SelectAll();
    }
}

public class CustomerServiceLoggingDecorator : ICustomerService
{
    private readonly ICustomerService _customerService;
    private readonly ILogger _log = LogManager.GetCurrentClassLogger();

    public CustomerServiceLoggingDecorator(ICustomerService customerService)
    {
        _customerService = customerService;
    }

    public IEnumerable<Customer> GetAllCustomers()
    {
        var stopwatch = Stopwatch.StartNew();
        var result =  _customerService.GetAllCustomers();

        stopwatch.Stop();

        _log.Trace("Querying for all customers took: {0}ms", stopwatch.Elapsed.TotalMilliseconds);
        return result;
    }
}

我目前具有这样的注册设置(此存根方法由Unity.Mvc创建):

        public static void RegisterTypes(IUnityContainer container)
        {
            // NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
            // container.LoadConfiguration();

            // TODO: Register your types here
            // container.RegisterType<IProductRepository, ProductRepository>();

            // Register the database context
            container.RegisterType<DbContext, CustomerDbContext>();

            // Register the repositories
            container.RegisterType<IGenericRepository<Customer>, GenericRepository<Customer>>();

            // Register the services

            // Register logging decorators
            // This way "works"*
            container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
            new InjectionConstructor(
                new CustomerService(
                    new GenericRepository<Customer>(
                        new CustomerDbContext()))));

            // This way seems more natural for DI but overflows the stack
            container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();

        }

因此,现在我不确定使用依赖项注入实际创建装饰器的正确"方法.我的装饰器基于Mark Seemann的回答此处.在他的示例中,他正在更新几个传递到类中的对象.这就是我的工作" *代码段的工作方式.但是,我想我错过了一个基本步骤.

为什么要像这样手动创建新对象?这是否否定了让容器为我解决的问题?还是我应该在这种方法中使用contain.Resolve()(服务定位器),以使所有依赖项仍然注入?

我对组合根"概念有点熟悉,在这个概念中,您应该将这些依赖关系集中在一个地方,然后仅在一个地方,然后级联到应用程序的较低层.那么Unity.Mvc生成的RegisterTypes()是ASP.NET MVC应用程序的组合根吗?如果是这样,直接在这里更新对象实际上是正确的吗?

给我的印象是,通常使用Unity时,您需要自己创建合成根,但是,Unity.Mvc是一个例外,因为它创建了自己的合成根,因为它似乎能够将依赖项注入到控制器中.在构造函数中具有诸如ICustomerService之类的接口,而无需我编写代码使其实现.

问题:我认为我缺少关键信息,由于循环依赖关系,这使我无法进入StackoverflowExceptions.在仍然遵循依赖项注入/控制原理和约定的倒置的同时,如何正确实现我的装饰器类?

第二个问题:如果我决定仅在某些情况下希望应用日志记录装饰器,那该怎么办?因此,如果我希望拥有CustomerServiceLoggingDecorator依赖项,但MyController2只需要普通的CustomerService,我该如何创建两个单独的注册?因为如果我这样做:

container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();
container.RegisterType<ICustomerService, CustomerService>();

这将被覆盖,这意味着两个控制器都将同时注入装饰器或正常服务.我该如何兼顾?

这不是重复的问题,因为我在循环依赖方面存在问题,并且对此缺乏正确的DI方法的理解.我的问题适用于整个概念,而不仅仅是链接问题之类的装饰器模式.

解决方案

序言

无论何时遇到DI容器(统一或其他问题)时,请问自己:纯DI .使用纯DI可以轻松回答所有答案.

团结

如果必须使用Unity,那么以下内容可能会有所帮助.自2011年以来我就没有使用过Unity,因此此后情况可能有所变化,但是请在我的书,这样的方法可能会解决问题:

container.RegisterType<ICustomerService, CustomerService>("custSvc");
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
    new InjectionConstructor(
        new ResolvedParameter<ICustomerService>("custSvc")));

或者,您也可以执行以下操作:

container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
    new InjectionConstructor(
        new ResolvedParameter<CustomerService>()));

此替代方法更易于维护,因为它不依赖命名服务,但具有(潜在的)缺点,即您无法通过ICustomerService界面解析CustomerService.无论如何,您可能都不应该这样做,所以应该不成问题,因此这可能是一个更好的选择.

I'm getting StackoverflowException's in my implementation of the decorator pattern when using dependency injection. I think it is because I'm "missing" something from my understanding of DI/IoC.

For example, I currently have CustomerService and CustomerServiceLoggingDecorator. Both classes implement ICustomerService, and all the decorator class does is use an injected ICustomerService but adds some simple NLog logging so that I can use logging without affecting the code in CustomerService while also not breaking the single responsibility principle.

However the problem here is that because CustomerServiceLoggingDecorator implements ICustomerService, and it also needs an implementation of ICustomerService injected into it to work, Unity will keep trying to resolve it back to itself which causes an infinite loop until it overflows the stack.

These are my services:

public interface ICustomerService
{
    IEnumerable<Customer> GetAllCustomers();
}

public class CustomerService : ICustomerService
{
    private readonly IGenericRepository<Customer> _customerRepository;

    public CustomerService(IGenericRepository<Customer> customerRepository)
    {
        if (customerRepository == null)
        {
            throw new ArgumentNullException(nameof(customerRepository));
        }

        _customerRepository = customerRepository;
    }

    public IEnumerable<Customer> GetAllCustomers()
    {
        return _customerRepository.SelectAll();
    }
}

public class CustomerServiceLoggingDecorator : ICustomerService
{
    private readonly ICustomerService _customerService;
    private readonly ILogger _log = LogManager.GetCurrentClassLogger();

    public CustomerServiceLoggingDecorator(ICustomerService customerService)
    {
        _customerService = customerService;
    }

    public IEnumerable<Customer> GetAllCustomers()
    {
        var stopwatch = Stopwatch.StartNew();
        var result =  _customerService.GetAllCustomers();

        stopwatch.Stop();

        _log.Trace("Querying for all customers took: {0}ms", stopwatch.Elapsed.TotalMilliseconds);
        return result;
    }
}

I currently have the registrations setup like this (This stub method was created by Unity.Mvc):

        public static void RegisterTypes(IUnityContainer container)
        {
            // NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
            // container.LoadConfiguration();

            // TODO: Register your types here
            // container.RegisterType<IProductRepository, ProductRepository>();

            // Register the database context
            container.RegisterType<DbContext, CustomerDbContext>();

            // Register the repositories
            container.RegisterType<IGenericRepository<Customer>, GenericRepository<Customer>>();

            // Register the services

            // Register logging decorators
            // This way "works"*
            container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
            new InjectionConstructor(
                new CustomerService(
                    new GenericRepository<Customer>(
                        new CustomerDbContext()))));

            // This way seems more natural for DI but overflows the stack
            container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();

        }

So now I'm not sure of the "proper" way of actually creating a decorator with dependency injection. I based my decorator on Mark Seemann's answer here. In his example, he is newing up several objects that get passed into the class. This is how my it "works"* snippet works. However, I think I have missed a fundamental step.

Why manually create new objects like this? Doesn't this negate the point of having the container doing the resolving for me? Or should I instead do contain.Resolve() (service locator) within this one method, to get all the dependencies injected still?

I'm slightly familiar with the "composition root" concept, which is where you are supposed to wire up these dependencies in one and only one place that then cascades down to the lower levels of the application. So is the Unity.Mvc generated RegisterTypes() the composition root of an ASP.NET MVC application? If so is it actually correct to be directly newing up objects here?

I was under the impression that generally with Unity you need to create the composition root yourself, however, Unity.Mvc is an exception to this in that it creates it's own composition root because it seems to be able to inject dependencies into controllers that have an interface such as ICustomerService in the constructor without me writing code to make it do that.

Question: I believe I'm missing a key piece of information, which is leading me to StackoverflowExceptions due to circular dependencies. How do I correctly implement my decorator class while still following dependency injection/inversion of control principles and conventions?

Second question: What about if I decided I only wanted to apply the logging decorator in certain circumstances? So if I had MyController1 that I wished to have a CustomerServiceLoggingDecorator dependency, but MyController2 only needs a normal CustomerService, how do I create two separate registrations? Because if I do:

container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>();
container.RegisterType<ICustomerService, CustomerService>();

Then one will be overwritten meaning that both controllers will either both have a decorator injected or a normal service injected. How do I allow for both?

Edit: This is not a duplicate question because I am having problems with circular dependencies and a lack of understanding of the correct DI approach for this. My question applies to a whole concept not just the decorator pattern like the linked question.

解决方案

Preamble

Whenever you are having trouble with a DI Container (Unity or otherwise), ask yourself this: is using a DI Container worth the effort?

In most cases, the answer ought to be no. Use Pure DI instead. All your answers are trivial to answer with Pure DI.

Unity

If you must use Unity, perhaps the following will be of help. I haven't used Unity since 2011, so things may have changed since then, but looking up the issue in section 14.3.3 in my book, something like this might do the trick:

container.RegisterType<ICustomerService, CustomerService>("custSvc");
container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
    new InjectionConstructor(
        new ResolvedParameter<ICustomerService>("custSvc")));

Alternatively, you may also be able to do this:

container.RegisterType<ICustomerService, CustomerServiceLoggingDecorator>(
    new InjectionConstructor(
        new ResolvedParameter<CustomerService>()));

This alternative is easier to maintain because it does not rely on named services, but has the (potential) disadvantage that you can't resolve CustomerService through the ICustomerService interface. You probably shouldn't be doing that anyway, so it ought not to be an issue, so this is probably a better alternative.

这篇关于依赖注入组合的根和装饰器模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆