与往常一样,它取决于™。答案取决于一个人要解决的问题。在这个答案中,我将尝试解决一些共同的动力:
支持较小的代码库
如果您有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...
}
因为是组合,所以这仍然适合于设计模式对类继承的偏爱。从可维护性的角度来看,这仍然可以认为是可维护的,因为如果需要更改组成,您只需编辑的源代码。Foo
Bar
Foo
这几乎比依赖注入更难以维护。实际上,我想说直接编辑使用的类要容易得多Bar
,而不必遵循依赖项注入所固有的间接调用。
在我关于依赖注入的书的第一版中,我对易失性和稳定依赖进行了区分。
易失性依赖项是您应考虑注入的那些依赖项。它们包括
- 编译后必须重新配置的依赖项
- 另一个团队并行开发的依赖关系
- 具有不确定性行为的依赖关系或具有副作用的行为
另一方面,稳定的依存关系是以良好定义的方式运行的依存关系。从某种意义上讲,您可能会争辩说,这种区别为粗粒度的依赖项注入提供了条件,尽管我必须承认,当我写这本书时我并没有完全意识到这一点。
但是,从测试的角度来看,这使单元测试更加困难。您不能再Foo
独立于进行单元测试Bar
。正如JB Rainsberger所解释的那样,集成测试遭受了复杂性的组合爆炸式增长。如果要通过4-5个类的集成涵盖所有路径,则实际上必须编写成千上万的测试用例。
与之相反的观点是,通常您的任务不是编写类。您的任务是开发一个解决一些特定问题的系统。这就是行为驱动开发(BDD)背后的动机。
DHH提出了另一种观点,他声称TDD会导致测试引起的设计损坏。他还喜欢粗粒度的集成测试。
如果您从软件开发的角度出发,那么粗粒度的依赖注入就很有意义。
细粒度依赖注入的情况
另一方面,细粒度的依赖注入可以描述为注入所有东西!
我对粗粒度依赖注入的主要关注是JB Rainsberger提出的批评。您无法使用集成测试涵盖所有代码路径,因为您实际上需要编写成千上万个测试用例才能覆盖所有代码路径。
BDD的支持者将反对这样一个论点,即您不需要用测试覆盖所有代码路径。您只需要涵盖产生业务价值的产品即可。
但是,以我的经验,所有“奇异”代码路径也都将在大批量部署中执行,并且如果不进行测试,其中的许多路径将具有缺陷并导致运行时异常(通常为空引用异常)。
这使我赞成细粒度的依赖注入,因为它使我能够隔离地测试所有对象的不变量。
偏爱函数式编程
尽管我倾向于细粒度的依赖注入,但由于其他原因,我将重点转移到了函数编程上,因为它本质上是可测试的。
您向SOLID代码迈进的越多,它变得越有用。迟早您也可以尝试一下。功能体系结构是端口和适配器体系结构,而依赖项注入也是端口和适配器的一种尝试。但是,不同之处在于,像Haskell这样的语言通过其类型系统来实现该体系结构。
支持静态类型的函数式编程
在这一点上,我已经基本上放弃了面向对象的编程(OOP),尽管OOP的许多问题与Java和C#之类的主流语言本质上是耦合在一起的,而不是概念本身。
主流的OOP语言存在的问题是,要避免组合爆炸问题几乎是不可能的,这种爆炸问题未经测试就导致了运行时异常。另一方面,诸如Haskell和F#之类的静态类型语言使您可以在类型系统中编码许多决策点。这意味着编译器将不必告诉您是否已经处理了所有可能的代码路径(一定程度上;这不是灵丹妙药),而不必编写数千个测试。
而且,依赖项注入不起作用。真正的函数式编程必须拒绝依赖的整个概念。结果是更简单的代码。
摘要
如果被迫使用C#,我更喜欢细粒度的依赖注入,因为它使我能够用可管理的测试用例覆盖整个代码库。
最后,我的动力是快速反馈。尽管如此,单元测试并不是获得反馈的唯一方法。