Unity注入过多的构造函数参数

Kon*_*nov 3 c# dependency-injection unity-container

我有以下Unity相关问题.下面的代码存根设置基本方案,问题位于底部.

注意,该[Dependency]属性不适用于下面的示例并导致StackoverflowException,但构造函数注入确实有效.

注意(2)下面的一些评论开始分配"标签",如代码气味,糟糕的设计等......因此,为了避免混淆,这里是没有任何设计的业务设置.

这个问题似乎引起了一些最着名的C#大师的严重争议.事实上,这个问题远远超出了C#,它更多地涉及纯粹的计算机科学.问题是基于服务定位器模式和纯依赖注入模式之间众所周知的"战斗":https://martinfowler.com/articles/injection.html vs http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern /以及后续更新以解决依赖注入过于复杂时的情况:http://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/

这是一种情况,它不能很好地适应前两种情况,但似乎完全符合第一种情况.

我有一个大的(50+)集合,我称之为微服务.如果您有更好的名字,请在阅读时"申请".它们中的每一个都在一个对象上运行,我们称之为引用.但是,元组(上下文+引用)似乎更合适.Quote是一个业务对象,它被处理并序列化为数据库,上下文是一些支持信息,这在处理报价时是必要的,但不保存到数据库中.其中一些支持信息实际上可能来自数据库或某些第三方服务.这是无关紧要的.装配线作为一个现实世界的例子浮现在脑海中:装配工人(微服务)接收一些输入(指令(上下文)+部件(引用)),处理它(根据指令和/或修改指令对部件做某事)并且如果成功则将其进一步传递或在发生问题时丢弃它(引发异常).微服务最终被捆绑到少数(约5个)高级服务中.这种方法线性化了一些非常复杂的业务对象的处理,并允许将每个微服务与所有其他服务分开测试:只需给它一个输入状态并测试它产生预期的输出.

这是有趣的地方.由于涉及的步骤数量多,高级服务开始依赖于许多微服务:10+以上.这种依赖性很自然,它只反映了底层业务对象的复杂性.最重要的是,微服务几乎可以在不断的基础上添加/删除:基本上,它们是一些业务规则,几乎像水一样流畅.

这与Mark上面的建议严重冲突:如果我在一些高级服务中有10个以上有效的独立规则应用于某个高级服务的引用,那么,根据第三个博客,我应该将它们聚合成一些逻辑组,比如说不超过3-4而不是注入所有10+通过构造函数.但是没有合乎逻辑的团体!虽然有些规则是松散依赖的,但大多数规则都没有,因此人为地将它们捆绑在一起会带来更多弊大于利.

抛出规则经常变化,它变成了维护的噩梦:每次规则改变时,所有真实/模拟的呼叫都必须更新.

我甚至没有提到规则是美国依赖的,因此,理论上,大约有50个规则集合,每个州和每个工作流程都有一个集合.虽然一些规则在所有州之间共享(例如"保存对数据库的引用"),但是有很多州的具体规则.

这是一个非常简单的例子.

引用 - 业务对象,它被保存到数据库中.

public class Quote
{
    public string SomeQuoteData { get; set; }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

微服务.他们每个人都会执行一些小的更新来引用.也可以从一些较低级别的微服务构建更高级别的服务.

public interface IService_1
{
    Quote DoSomething_1(Quote quote);
}
// ...

public interface IService_N
{
    Quote DoSomething_N(Quote quote);
}
Run Code Online (Sandbox Code Playgroud)

所有微服务都继承自此接口.

public interface IQuoteProcessor
{
    List<Func<Quote, Quote>> QuotePipeline { get; }
    Quote ProcessQuote(Quote quote = null);
}

// Low level quote processor. It does all workflow related work.
public abstract class QuoteProcessor : IQuoteProcessor
{
    public abstract List<Func<Quote, Quote>> QuotePipeline { get; }

    public Quote ProcessQuote(Quote quote = null)
    {
        // Perform Aggregate over QuotePipeline.
        // That applies each step from workflow to a quote.
        return quote;
    }
}
Run Code Online (Sandbox Code Playgroud)

高级"工作流程"服务之一.

public interface IQuoteCreateService
{
    Quote CreateQuote(Quote quote = null);
}
Run Code Online (Sandbox Code Playgroud)

以及我们使用许多低级微服务的实际实施.

public class QuoteCreateService : QuoteProcessor, IQuoteCreateService
{
    protected IService_1 Service_1;
    // ...
    protected IService_N Service_N;

    public override List<Func<Quote, Quote>> QuotePipeline =>
        new List<Func<Quote, Quote>>
        {
            Service_1.DoSomething_1,
            // ...
            Service_N.DoSomething_N
        };

    public Quote CreateQuote(Quote quote = null) => 
        ProcessQuote(quote);
}
Run Code Online (Sandbox Code Playgroud)

实现DI有两种主要方式:

标准方法是通过构造函数注入所有依赖项:

    public QuoteCreateService(
        IService_1 service_1,
        // ...
        IService_N service_N
        )
    {
        Service_1 = service_1;
        // ...
        Service_N = service_N;
    }
Run Code Online (Sandbox Code Playgroud)

然后使用Unity注册所有类型:

public static class UnityHelper
{
    public static void RegisterTypes(this IUnityContainer container)
    {
        container.RegisterType<IService_1, Service_1>(
            new ContainerControlledLifetimeManager());
        // ...
        container.RegisterType<IService_N, Service_N>(
            new ContainerControlledLifetimeManager());

        container.RegisterType<IQuoteCreateService, QuoteCreateService>(
            new ContainerControlledLifetimeManager());
    }
}
Run Code Online (Sandbox Code Playgroud)

然后Unity将在运行时执行其"魔术"并解决所有服务.问题是目前我们有大约30个这样的微服务,预计这个数字会增加.随后,一些构造函数已经注入了10多个服务.这对维护,模拟等不方便......

当然,可以从这里使用这个想法:http://blog.ploeh.dk/2010/02/02/RefactoringtoAggregateServices/然而,微服务并不真正相互关联,因此将它们捆绑在一起是一个人为的过程没有任何理由.此外,它还将失去使整个工作流程线性和独立的目的(微服务采用当前"状态",然后用引号预先形成一些动作,然后继续前进).他们中没有人关心他们之前或之后的任何其他微服务.

另一个想法似乎是创建一个"服务存储库":

public interface IServiceRepository
{
    IService_1 Service_1 { get; set; }
    // ...
    IService_N Service_N { get; set; }

    IQuoteCreateService QuoteCreateService { get; set; }
}

public class ServiceRepository : IServiceRepository
{
    protected IUnityContainer Container { get; }

    public ServiceRepository(IUnityContainer container)
    {
        Container = container;
    }

    private IService_1 _service_1;

    public IService_1 Service_1
    {
        get => _service_1 ?? (_service_1 = Container.Resolve<IService_1>());
        set => _service_1 = value;
    }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

然后在Unity中注册它并将所有相关服务的构造函数更改为以下内容:

    public QuoteCreateService(IServiceRepository repo)
    {
        Service_1 = repo.Service_1;
        // ...
        Service_N = repo.Service_N;
    }
Run Code Online (Sandbox Code Playgroud)

这种方法的好处(与前一种方法相比)如下:

所有微服务和更高级别的服务都可以以统一的形式创建:可以轻松添加/删除新的微服务,而无需修复服务和所有单元测试的构造函数调用.随后,维护和复杂性降低.

由于界面IServiceRepository,很容易创建一个自动单元测试,它将遍历所有属性并验证所有服务都可以实例化,这意味着不会有令人讨厌的运行时意外.

这种方法的问题在于它开始看起来很像服务定位器,有些人认为这是一种反模式:http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/然后是人们开始争辩说,所有依赖关系必须明确而不是隐藏ServiceRepository.

我该怎么办?

Chr*_*rdt 5

我只想创建一个界面:

public interface IDoSomethingAble
{
    Quote DoSomething(Quote quote);
}
Run Code Online (Sandbox Code Playgroud)

和一个聚合:

public interface IDoSomethingAggregate : IDoSomethingAble {}

public class DoSomethingAggregate : IDoSomethingAggregate 
{
    private IEnumerable<IDoSomethingAble> somethingAbles;

    public class DoSomethingAggregate(IEnumerable<IDoSomethingAble> somethingAbles)
    {
        _somethingAbles = somethingAbles;
    }

    public Quote DoSomething(Quote quote)
    {
        foreach(var somethingAble in _somethingAbles)
        {
            somethingAble.DoSomething(quote);
        }
        return quote;
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:依赖注入并不意味着,您需要在任何地方使用它.

我会去工厂:

public class DoSomethingAggregateFactory
{
    public IDoSomethingAggregate Create()
    {
        return new DoSomethingAggregate(GetItems());
    }

    private IEnumerable<IDoSomethingAble> GetItems()
    {
        yield return new Service1();
        yield return new Service2();
        yield return new Service3();
        yield return new Service4();
        yield return new Service5();
    }
}
Run Code Online (Sandbox Code Playgroud)

其他所有内容(不是构造函数注入)隐藏了显式依赖项.


作为最后的手段,您还可以创建一些DTO对象,通过构造函数注入所有需要的服务(但只有一次).

通过这种方式,您可以请求ProcessorServiceScope并提供所有服务,而无需为每个类创建ctor逻辑:

public class ProcessorServiceScope
{
    public Service1 Service1 {get;};
    public ServiceN ServiceN {get;};

    public ProcessorServiceScope(Service1 service1, ServiceN serviceN)
    {
        Service1 = service1;
        ServiceN = serviceN;
    }
}

public class Processor1
{
    public Processor1(ProcessorServiceScope serviceScope)
    {
        //...
    }
}

public class ProcessorN
{
    public ProcessorN(ProcessorServiceScope serviceScope)
    {
        //...
    }
}
Run Code Online (Sandbox Code Playgroud)

它似乎是一个ServiceLocator,但它并没有隐藏依赖关系,所以我认为这是好的.