依赖注入多少钱?


38

我在一个项目中工作,该项目使用(Spring)依赖注入来处理实际上是类依赖的所有内容。我们现在已经将Spring配置文件增加到大约4000行。不久前,我观看了Bob叔叔在YouTube上的一次演讲(不幸的是,我找不到该链接),在该演讲中,他建议仅将几个中央依赖项(例如工厂,数据库等)注入到Main组件中,然后从它们分布。

这种方法的优点是将DI框架与应用程序的大部分解耦,并且还使Spring配置更干净,因为工厂将包含以前配置中的更多内容。相反,这将导致创建逻辑在许多工厂类之间传播,并且测试可能会变得更加困难。

因此,我的问题确实是,您会在一种或另一种方法中看到哪些其他优点或缺点?有没有最佳做法?非常感谢你的回答!


3
拥有4000行配置文件并不是依赖注入的问题...有很多方法可以解决它:将文件模块化为多个较小的文件,切换到基于注释的注入,使用JavaConfig文件而不是xml。基本上,您遇到的问题是无法管理应用程序依赖项注入需求的复杂性和大小。
Maybe_Factor

1
@Maybe_Factor TL; DR将项目拆分为较小的组件。
Walfrat

Answers:


44

与往常一样,它取决于™。答案取决于一个人要解决的问题。在这个答案中,我将尝试解决一些共同的动力:

支持较小的代码库

如果您有4,000行Spring配置代码,我想代码库中有数千个类。

事后您很难解决这个问题,但是根据经验,我倾向于使用具有较小代码库的小型应用程序。如果您是Domain-Driven Design的一部分,那么您可以,例如,为每个受限上下文创建代码库。

由于我在大多数职业生涯中都编写了基于Web的业务线代码,因此我会根据自己的有限经验提供此建议。我可以想象,如果您正在开发桌面应用程序,嵌入式系统或其他应用程序,那么事情就很难拆开。

虽然我确实意识到这条第一条建议很不实用,但我也认为这是最重要的,因此我将其包括在内。代码的复杂度随代码库的大小呈非线性(可能呈指数形式)变化。

偏爱纯DI

虽然我仍然意识到这个问题代表了一种现状,但我还是建议使用Pure DI。不要使用DI容器,但如果这样做,至少要使用它来实现基于约定的组合

我没有使用Spring的任何实际经验,但是我假设通过配置文件隐含一个XML文件。

使用XML配置依赖关系是两全其美。首先,您失去了编译时类型的安全性,但是却一无所获。XML配置文件可以很容易地与它尝试替换的代码一样大。

与它要解决的问题相比,依赖项注入配置文件在配置复杂性时钟上占据了错误的位置。

粗粒度依赖注入的情况

我可以为粗粒度的依赖注入提供理由。我还可以提出细粒度的依赖项注入(请参阅下一节)。

如果只注入一些“中央”依赖项,那么大多数类可能看起来像这样:

public class Foo
{
    private readonly Bar bar;

    public Foo()
    {
        this.bar = new Bar();
    }

    // Members go here...
}

因为是组合,所以这仍然适合于设计模式对类继承偏爱。从可维护性的角度来看,这仍然可以认为是可维护的,因为如果需要更改组成,您只需编辑的源代码。FooBarFoo

这几乎比依赖注入更难以维护。实际上,我想说直接编辑使用的类要容易得多Bar,而不必遵循依赖项注入所固有的间接调用。

我关于依赖注入的书的第一版中,我对易失性和稳定依赖进行了区分。

易失性依赖项是您应考虑注入的那些依赖项。它们包括

  • 编译后必须重新配置的依赖项
  • 另一个团队并行开发的依赖关系
  • 具有不确定性行为的依赖关系或具有副作用的行为

另一方面,稳定的依存关系是以良好定义的方式运行的依存关系。从某种意义上讲,您可能会争辩说,这种区别为粗粒度的依赖项注入提供了条件,尽管我必须承认,当我写这本书时我并没有完全意识到这一点。

但是,从测试的角度来看,这使单元测试更加困难。您不能再Foo独立于进行单元测试Bar。正如JB Rainsberger所解释的那样,集成测试遭受了复杂性的组合爆炸式增长。如果要通过4-5个类的集成涵盖所有路径,则实际上必须编写成千上万的测试用例。

与之相反的观点是,通常您的任务不是编写类。您的任务是开发一个解决一些特定问题的系统。这就是行为驱动开发(BDD)背后的动机。

DHH提出了另一种观点,他声称TDD会导致测试引起的设计损坏。他还喜欢粗粒度的集成测试。

如果您从软件开发的角度出发,那么粗粒度的依赖注入就很有意义。

细粒度依赖注入的情况

另一方面,细粒度的依赖注入可以描述为注入所有东西!

我对粗粒度依赖注入的主要关注是JB Rainsberger提出的批评。您无法使用集成测试涵盖所有代码路径,因为您实际上需要编写成千上万个测试用例才能覆盖所有代码路径。

BDD的支持者将反对这样一个论点,即您不需要用测试覆盖所有代码路径。您只需要涵盖产生业务价值的产品即可。

但是,以我的经验,所有“奇异”代码路径也都将在大批量部署中执行,并且如果不进行测试,其中的许多路径将具有缺陷并导致运行时异常(通常为空引用异常)。

这使我赞成细粒度的依赖注入,因为它使我能够隔离地测试所有对象的不变量。

偏爱函数式编程

尽管我倾向于细粒度的依赖注入,但由于其他原因,我将重点转移到了函数编程上,因为它本质上是可测试的

您向SOLID代码迈进的越多,它变得越有用。迟早您也可以尝试一下。功能体系结构是端口和适配器体系结构,而依赖项注入也是端口和适配器的一种尝试。但是,不同之处在于,像Haskell这样的语言通过其类型系统来实现该体系结构。

支持静态类型的函数式编程

在这一点上,我已经基本上放弃了面向对象的编程(OOP),尽管OOP的许多问题与Java和C#之类的主流语言本质上是耦合在一起的,而不是概念本身。

主流的OOP语言存在的问题是,要避免组合爆炸问题几乎是不可能的,这种爆炸问题未经测试就导致了运行时异常。另一方面,诸如Haskell和F#之类的静态类型语言使您可以在类型系统中编码许多决策点。这意味着编译器将不必告诉您是否已经处理了所有可能的代码路径(一定程度上;这不是灵丹妙药),而不必编写数千个测试。

而且,依赖项注入不起作用。真正的函数式编程必须拒绝依赖的整个概念。结果是更简单的代码。

摘要

如果被迫使用C#,我更喜欢细粒度的依赖注入,因为它使我能够用可管理的测试用例覆盖整个代码库。

最后,我的动力是快速反馈。尽管如此,单元测试并不是获得反馈的唯一方法


1
我真的很喜欢这个答案,尤其是在Spring上下文中使用的语句:“使用XML配置依赖关系是最糟糕的两个方面。首先,您失去了编译时类型安全性,但是却一无所获。XML配置文件可以很容易地与它尝试替换的代码一样大。”
Thomas Carlisle

@ThomasCarlisle不是XML配置的重点,您不必触摸代码(甚至编译)即可更改它吗?由于Mark提到的两件事,我从未(或几乎没有)使用过它,但是您确实得到了一些回报。
El Mac

1

大量的DI设置类是一个问题。但是考虑一下它所取代的代码。

您需要实例化所有这些服务等等。这样做的代码要么在app.main()起点并手动注入,要么this.myService = new MyService();在类中紧密耦合。

通过将安装程序类划分为多个安装程序类来减小安装程序类的大小,并从程序开始点对其进行调用。即。

main()
{
   var c = new diContainer();
   var service1 = diSetupClass.SetupService1(c);
   var service2 = diSetupClass.SetupService2(c, service1); //if service1 is required by service2
   //etc

   //main logic
}

您无需引用service1或2,只需将它们传递给其他ci设置方法即可。

这比尝试从容器中获取它们要好,因为它会强制执行调用设置的顺序。


1
对于那些想进一步研究这个概念(在程序开始点构建类树)的人,搜索的最佳术语是“ composition root”
e_i_pi

@Ewan那是什么ci变量?
superjos

您的ci设置类具有静态方法
Ewan

敦促应迪。我一直在想“容器”
Ewan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.