【问题标题】:How to minimize the amount of code to write when applying dependency injection?应用依赖注入时如何最小化代码量?
【发布时间】:2022-07-25 18:28:10
【问题描述】:

这让我很烦很长时间。许多 c# 特性都是关于减少代码和冗余的。但是我们有依赖注入。而这个功能需要我们指定我们想要的东西 3 次

public class MyClass
{
    private readonly ServiceType _service; // first

    public MyClass(ServiceType service) // second
    {
        _service = service; // third
    }
}

现在假设您需要注入 10 个服务。玩得开心滚动。

现在我们已经有了通过公共属性初始化一个类的好模式,我们甚至可以拥有仅初始化属性,所以这种模式变得越来越像构造函数。但据我所知,依赖注入当然不支持这一点......

你知道有什么框架会使用它吗?有什么理由说明它还不是 .NET Core MVC 中的默认 DI 模式?

public class MyClass
{
    public ServiceType SomeService {get; set; }
}

var property = new MyClass { SomeService = new ServiceType() };

没那么难。只是需要一点反思。

【问题讨论】:

  • 我不明白你的例子。 new MyClass { SomeService = new ServiceType() } 不是依赖注入。您只是在问 DI 是否可以使用属性而不是构造函数参数?
  • 我不认为 .NET 中的默认依赖注入器支持属性注入,但其他 DI 框架可能。它可能仅部分取决于它是否可能,更多地取决于作者是否认为这是要走的路。有些人会争辩说构造函数注入更加强制,因为没有什么可以阻止使用代码来实例化一个对象而不是设置它的属性。
  • 属性有一种只是可选的味道,但这些依赖项不是可选的。说您没有强制使用内置的 di 容器。只需使用更适合您的第三方。
  • 是的,我在问这个问题或类似问题,我很高兴看到任何将 3 个不必要的参数提及次数减少到 1 个的模式

标签: c# dependency-injection


【解决方案1】:

如果我的理解正确,您想知道在应用依赖注入时是否有办法(在 C# 中)最大限度地减少您的类中所需的代码量(管道)。我相信最近的 C# 版本确实提供了这方面的解决方案,而不必回退到代码异味和反模式。

我想做一个重要的说明,即如果您“需要注入 10 个服务”,您正在应用构造函数过度注入代码气味。有许多可用的设计模式和重构可以帮助您避免创建具有超过四个或五个依赖项的类。我将在这里详细讨论有关构造函数过度注入的问题和解决方案。相反,我想参考我的书的第 6.1 节,Dependency Injection Principles, Practices, and Patterns。不过,我的经验是,有四个或五个依赖项,由 DI 引起的“垃圾”数量是可行的。

您建议使用Property Injection 作为解决方案,以最大程度地减少启用松散耦合造成的麻烦。然而,属性注入会导致 temporal coupling 设计异味,即使在仅 init 属性的情况下也是如此。除此之外,Constructor Over-Injection 的根本问题是类有太多的依赖关系;它可能违反了Single-Responsibility Principle。使用属性注入并没有改变这一事实。因此,不建议使用属性注入而不是构造函数注入。

相反,C# 9 引入了Record types 的概念,它提供了一种减少杂乱无章的卓越方法,而无需求助于诸如时间耦合之类的代码气味。使用 C# 9 的记录类型语法,您的 MyClass 示例可以简化为以下内容:

public record MyClass(ServiceType service)
{

}

如果MyClass 有五个依赖项,它可能如下所示:

public record MyClass(
    IOrderRepository repository,
    IMessageService messageService,
    IBillingSystem billingSystem,
    ILocationService locationService,
    IInventoryManagement inventoryManagement)    
{
}

记录类型在其定义中包含其依赖项,C# 编译器将自动为您的依赖项和分配生成(除其他外)构造函数、(公共)成员。这意味着如果没有所需的依赖项(这是构造函数注入的一个重要特性),仍然无法创建类型。

最重要的缺点 (IMO) 是缺少前置条件检查,例如空值检查。可以将null 值注入MyClass(通过调用new MyClass(null),这在手动实现构造函数时可能会被阻止。然而,对我来说,这是一个很小的代价,特别是如果你使用防止null被注入的DI容器。未来版本的C#可能会克服这个缺点,因为它允许基于简写语法生成空检查。不幸的是,这个功能在过去被延迟了好几次。它是不清楚何时将此功能添加到语言中。

【讨论】:

  • 好的,这很有趣,因为我最近对这些记录和结果提出了一个问题:不要将其用于服务。但是我通常是最后一个关心的人。但是,记录方法有一个非常重要的缺点。它不能从类派生。所以在 MVC 中它不适用于控制器,因为 ControllerBase 是一个类。所以,想象一个控制器需要 2 个存储库。例如一个用于获取您的应用程序配置,另一个用于获取特定模型,您仍然希望使用依赖注入。你是做什么的?
  • 您对从基类继承的限制是正确的。您的大多数应用程序组件将(也不应该)从基类派生,因此这是一个只会影响少数部分的限制,例如在编写 MVC 控制器类时。在这方面我唯一的“解决方案”是:对于这种情况,只需回退到常规类并自己实现构造函数。正如我在回答中所说的那样,应用程序组件不应该得到那么多依赖。
  • true,但是构造函数是你应用程序的入口点,也是最有可能获得最多依赖的点。假设您有一个配置存储库,一个桌面存储库,并且桌面是一个用于桌面预订的存储库,如果您的配置过于复杂,您可能会有不同级别的配置存储库,例如提供程序配置存储库、参数配置存储库和用户设置存储库,这可能所有这些都可能被控制器使用。但也许在以后的 c# 版本中我们会得到一个解决方案......
  • 嗨@Kyoshiro,恐怕我不明白你的最后评论。 “构造函数是你[r]应用程序的入口点”是什么意思?我还想强调,我总是成功地将控制器中的依赖项数量保持在较低水平,我建议您也这样做。在这方面有许多模式和实践可以提供帮助。您可以考虑应用 Facade Service 重构、应用消息传递模式、使用装饰器来处理横切关注点、将大控制器拆分为较小的控制器。我的书中讨论了其中许多选项(参见 6.1 和 10.3)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多