我试图在工作中引入依赖注入(DI)模式,我们的一位主要开发人员想知道:如果有的话,使用依赖注入模式的缺点是什么?

注意,如果可能的话,我在这里寻找一个详尽的列表,而不是关于这个主题的主观讨论。


澄清:我谈论的是依赖注入模式(参见Martin Fowler的这篇文章),而不是特定的框架,无论是基于xml的(如Spring)还是基于代码的(如Guice),还是“自滚动”的框架。


Reddit的subreddit /r/programming上正在进行一些深入的讨论/咆哮/辩论。


当前回答

没有任何DI的代码会面临众所周知的陷入意大利面条代码的风险——一些症状是类和方法太大,做太多,不容易更改、分解、重构或测试。

大量使用DI的代码可以是Ravioli代码,其中每个小类就像一个单独的Ravioli块——它只做一件小事,并且遵循单一责任原则,这是很好的。但是从类本身来看,很难看到系统作为一个整体在做什么,因为这取决于所有这些小部分是如何组合在一起的,这是很难看到的。它看起来就像一大堆小东西。

通过避免大型类中大量耦合代码的意大利面复杂性,您将面临另一种复杂性的风险,其中有许多简单的小类,它们之间的交互非常复杂。

我不认为这是一个致命的缺点- DI仍然是非常值得的。在某种程度上,小班级只做一件事的馄饨风格可能是好的。即使过度,我也不认为它像意大利面条代码那样糟糕。但是要避免它,第一步是要意识到它可能会走得太远。点击链接了解如何避免这种情况。

其他回答

当您不断地使用技术来处理静态类型时,静态类型语言的假定好处似乎大大减少了。我刚刚采访的一家大型Java商店正在用静态代码分析绘制他们的构建依赖关系……它必须解析所有的Spring文件才能有效。

同样的基本问题,你经常遇到的面向对象编程,样式规则和其他一切。这是可能的——事实上是非常常见的——做太多的抽象,添加太多的间接,并且通常在错误的地方过度地应用好的技术。

您应用的每个模式或其他构造都会带来复杂性。抽象和间接分散了信息,有时会移除了无关的细节,但有时也会让人更难理解到底发生了什么。你应用的每一条规则都会带来不灵活性,排除了可能是最佳方法的选择。

重点是编写能够完成这项工作的代码,并且是健壮的、可读的和可维护的。你是软件开发人员,而不是象牙塔建造者。

相关的链接

平台内效应

不要让建筑宇航员吓到你


可能依赖注入最简单的形式(别笑)是一个参数。依赖代码依赖于数据,而数据是通过传递参数的方式注入的。

是的,这很愚蠢,而且它没有解决依赖注入的面向对象问题,但是函数式程序员会告诉你(如果你有第一类函数)这是你唯一需要的依赖注入。这里的重点是举一个简单的例子,并展示潜在的问题。

以这个简单的传统函数为例。c++语法在这里并不重要,但我必须以某种方式拼写它……

void Say_Hello_World ()
{
  std::cout << "Hello World" << std::endl;
}

我有一个依赖,我想提取出来并注入-文本“Hello World”。很容易…

void Say_Something (const char *p_text)
{
  std::cout << p_text << std::endl;
}

为什么它比原来的更不灵活呢?如果我决定输出应该是Unicode。我可能想从std::cout切换到std::wcout。但这意味着我的字符串必须是*wchar_*t,而不是char类型的。要么必须更改每个调用方,要么(更合理地)将旧的实现替换为转换字符串并调用新实现的适配器。

这是维修工作如果我们保留原来的就不需要了。

如果它看起来微不足道,那么看看这个来自Win32 API的真实函数……

函数(winuser.h)

这有12个“依赖项”需要处理。例如,如果屏幕分辨率变得非常大,也许我们将需要64位的坐标值-和另一个版本的CreateWindowEx。是的,已经有一个旧版本仍然存在,它可能会在幕后映射到新版本……

创造温杜瓦宏(winuser宏)

这些“依赖关系”不仅仅是原始开发人员的问题——每个使用该接口的人都必须查找依赖关系是什么,它们是如何指定的,以及它们的含义,并确定为他们的应用程序做什么。这就是“明智的默认”可以让生活简单得多的地方。

面向对象的依赖注入在原理上没有什么不同。无论是在源代码文本中还是在开发人员时间中,编写类都是一种开销,如果编写该类是为了根据某些依赖对象规范提供依赖项,那么依赖对象将被锁定为支持该接口,即使需要替换该对象的实现。

这些都不应该被解读为依赖注入是不好的——远非如此。但是任何好的技术都可能被过度地应用在错误的地方。就像不是每个字符串都需要提取出来并转化为参数一样,也不是每个低级行为都需要从高级对象中提取出来并转化为可注入的依赖项。

依赖注入是一种技术或模式,与任何框架无关。您可以手动连接依赖项。DI帮助您实现SR(单一职责)和SoC(关注点分离)。DI会带来更好的设计。从我的观点和经验来看,没有坏处。就像任何其他模式一样,你可能会弄错或误用它(但在DI的情况下很难)。

如果您使用框架将DI作为原则引入到遗留应用程序中,那么您可能犯的最大错误就是将其误用为服务定位器。DI+框架本身是伟大的,只是让事情变得更好,我看到它!从组织的角度来看,每一个新的过程、技术、模式……都有共同的问题:

你必须训练你的团队 您必须更改您的应用程序(这包括风险)

一般来说,你必须投入时间和金钱,除此之外,没有任何负面影响,真的!

仅仅通过实现依赖注入就实现了代码的解耦,而没有实际解耦。我认为这是DI最危险的地方。

控制反转(不是完全依赖注入,但已经足够接近了)最大的“缺点”是,它倾向于去掉一个点来查看一个算法的概述。这基本上就是当你有解耦的代码时所发生的事情——在一个地方查看的能力是紧密耦合的产物。