依赖注入组合根和装饰器模式

时间:2016-03-16 23:05:12

标签: c# asp.net-mvc dependency-injection inversion-of-control unity-container

在使用依赖注入时,我在执行装饰器模式时获得StackoverflowException。我认为这是因为我错过了#34;我对DI / IoC的理解。

例如,我目前有CustomerServiceCustomerServiceLoggingDecorator。这两个类都实现ICustomerService,并且所有装饰器类都使用注入的ICustomerService但添加了一些简单的NLog日志记录,以便我可以使用日志记录而不会影响CustomerService中的代码,同时也不会破坏单一责任原则。

但问题是,因为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>();

        }

所以现在我不确定&#34;正确&#34;实际创建具有依赖注入的装饰器的方法。我的设计师基于Mark Seemann的回答here。在他的例子中,他正在新建几个传递到课堂上的对象。这就是我的#&#34;工作原理&#34; *代码段的工作原理。但是,我认为我错过了一个基本步骤。

为什么要手动创建这样的新对象?这不能否定让容器为我解决问题吗?或者我应该在这个方法中做contain.Resolve()(服务定位器),以便仍然注入所有依赖项?

我稍微熟悉&#34;组合词根&#34;概念,这是你应该在一个且只有一个地方连接这些依赖项的地方,然后再级联到应用程序的较低级别。那么Unity.Mvc生成RegisterTypes() ASP.NET MVC应用程序的组合根也是如此?如果是这样,直接在这里新建对象真的是正确的吗?

我的印象是,通常在Unity中你需要自己创建组合根,但是,Unity.Mvc是一个例外,因为它创建了它自己的组合根,因为它似乎是能够将依赖项注入到构造函数中具有ICustomerService接口的控制器中,而无需编写代码来实现此目的。

问题:我相信我错过了一条关键信息,由于循环依赖关系导致我StackoverflowExceptions。如何在仍遵循控制原则和约定的依赖注入/反转的同时正确实现我的装饰器类?

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

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

然后将覆盖一个,这意味着两个控制器都将注入装饰器或注入正常服务。我如何允许两者?

编辑:这不是一个重复的问题,因为我遇到循环依赖问题,并且缺乏对正确DI方法的理解。我的问题不仅适用于整个概念,而且还适用于像链接问题一样的装饰模式。

3 个答案:

答案 0 :(得分:19)

序言

每当您遇到DI容器(Unity或其他)时遇到问题,请问问自己:is using a DI Container worth the effort?

在大多数情况下,答案应该是。请改用Pure DI。您的所有答案都很容易回答Pure DI。

统一

如果你必须使用Unity,也许以下内容会有所帮助。我从2011年开始就没有使用过Unity,所以事情可能从那时起发生了变化,但在my book的第14.3.3节中查找问题,这样的事情可能就是这样:

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

或者,您也可以这样做:

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

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

答案 1 :(得分:2)

  

问题:我相信我错过了一条关键信息,由于循环依赖性导致我转向StackoverflowExceptions。如何在仍遵循控制原则和约定的依赖注入/反转的同时正确实现我的装饰器类?

正如已经指出的那样,最好的方法是使用以下结构。

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

这允许您指定如何按类型解析参数。您也可以按名称执行此操作,但类型是一个更干净的实现,并允许在编译期间更好地检查,因为字符串中的更改或错误类型将不会被捕获。 请注意,此代码部分与Mark Seemann提供的代码之间的唯一微小差异是InjectionConstructor拼写的更正。我不再详细说明这一部分了,因为没有其他任何补充,Mark Seemann尚未解释过。

  

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

您可以使用上面指定的方式使用Fluent表示法或使用带有依赖性覆盖的命名依赖项来执行此操作。

流利

这将向容器注册控制器,并在构造函数中指定该类型的超载。我更喜欢这种方法,但它只取决于你想要指定类型的位置。

container.RegisterType<MyController2>(
    new InjectionConstructor(new ResolvedParameter<CustomerService>()));

命名依赖

你这样做的方式完全相同,你可以像这样注册它们。

container.RegisterType<ICustomerService, CustomerService>("plainService");

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

这里的区别在于您使用命名依赖项代替可以使用相同接口解析的其他类型。这是因为每次Unity完成解析时,都需要将接口解析为一种具体类型,因此您不能拥有多个未命名的注册类型,这些注册类型已注册到同一接口。现在,您可以使用属性在控制器构造函数中指定覆盖。我的示例是名为MyController2的控制器,我在注册时添加了Dependency属性,其名称也在上面指定。因此,对于此构造函数,将注入CustomerService类型而不是默认的CustomerServiceLoggingDecorator类型。 MyController1仍会使用ICustomerService类型为CustomerServiceLoggingDecorator的默认未命名注册。

public MyController2([Dependency("plainService")]ICustomerService service) 

public MyController1(ICustomerService service) 

当您手动解析容器本身的类型时,还有一些方法可以执行此操作,请参阅Resolving Objects by Using Overrides。这里的问题是您需要访问容器本身才能执行此操作,这是不推荐的。作为替代方案,您可以在容器周围创建一个包装器,然后将其注入Controller(或其他类型),然后使用覆盖检索一种类型。再次,这有点乱,我会尽可能避免它。

答案 2 :(得分:0)

根据Mark的第二个答案,我希望将CustomerService注册为InjectionFactory并仅将其注册为服务类型,而不使用它的界面:

containter.RegisterType<CustomerService>(new InjectionFactory(
    container => new CustomerService(containter.Resolve<IGenericRepository<Customer>>())));

这将允许您在Mark的答案中注册日志对象,如:

containter.RegisterType<ICutomerService, CutomerServiceLoggingDecorator>(new InjectionConstructor(
    new ResolvedParameter<CustomerService>()));

这基本上与我在需要延迟加载时使用的技术相同,因为我不希望我的对象依赖于Lazy<IService>并且通过将它们包装在代理中允许我只注入{{1但是它通过代理懒洋洋地解决了。

这也允许您选择注册日志对象或普通对象的位置而不是需要IService字符串,只需为对象而不是{{1}解析magic }}

对于记录CustomerService

ICustomerService

或非登录CustomerService

container.Resolve<ICustomerService>()