我试图在工作中引入依赖注入(DI)模式,我们的一位主要开发人员想知道:如果有的话,使用依赖注入模式的缺点是什么?
注意,如果可能的话,我在这里寻找一个详尽的列表,而不是关于这个主题的主观讨论。
澄清:我谈论的是依赖注入模式(参见Martin Fowler的这篇文章),而不是特定的框架,无论是基于xml的(如Spring)还是基于代码的(如Guice),还是“自滚动”的框架。
Reddit的subreddit /r/programming上正在进行一些深入的讨论/咆哮/辩论。
我试图在工作中引入依赖注入(DI)模式,我们的一位主要开发人员想知道:如果有的话,使用依赖注入模式的缺点是什么?
注意,如果可能的话,我在这里寻找一个详尽的列表,而不是关于这个主题的主观讨论。
澄清:我谈论的是依赖注入模式(参见Martin Fowler的这篇文章),而不是特定的框架,无论是基于xml的(如Spring)还是基于代码的(如Guice),还是“自滚动”的框架。
Reddit的subreddit /r/programming上正在进行一些深入的讨论/咆哮/辩论。
当前回答
两件事:
它们需要额外的工具支持来检查配置是否有效。
例如,IntelliJ(商业版)支持检查Spring配置的有效性,并将标记出配置中的类型违反等错误。如果没有这种工具支持,就无法在运行测试之前检查配置是否有效。
这就是为什么“蛋糕”模式(Scala社区所熟知)是一个好主意的原因之一:组件之间的连接可以由类型检查器检查。而注释或XML则没有这种好处。
这使得程序的全局静态分析非常困难。
像Spring或Guice这样的框架很难静态地确定容器创建的对象图将是什么样子。尽管它们在容器启动时创建了一个对象图,但它们没有提供有用的api来描述将要创建的对象图。
其他回答
在过去的6个月里,我一直在广泛使用Guice (Java DI框架)。虽然总的来说我认为它很棒(特别是从测试的角度来看),但也有一些缺点。最值得注意的是:
Code can become harder to understand. Dependency injection can be used in very... creative... ways. For example I just came across some code that used a custom annotation to inject a certain IOStreams (eg: @Server1Stream, @Server2Stream). While this does work, and I'll admit has a certain elegance, it makes understanding the Guice injections a prerequisite to understanding the code. Higher learning curve when learning project. This is related to point 1. In order to understand how a project that uses dependency injection works, you need to understand both the dependency injection pattern and the specific framework. When I started at my current job I spent quite a few confused hours groking what Guice was doing behind the scenes. Constructors become large. Although this can be largely resolved with a default constructor or a factory. Errors can be obfuscated. My most recent example of this was I had a collision on 2 flag names. Guice swallowed the error silently and one of my flags wasn't initialized. Errors are pushed to run-time. If you configure your Guice module incorrectly (circular reference, bad binding, ...) most of the errors are not uncovered during compile-time. Instead, the errors are exposed when the program is actually run.
既然我已经抱怨过了。让我说,我将继续在我当前的项目中(很可能是在下一个项目中)使用Guice。依赖注入是一种非常强大的模式。但它肯定会让人困惑,无论你选择什么依赖注入框架,你几乎肯定会花一些时间咒骂。
另外,我同意其他发帖者的观点,依赖注入可能被过度使用。
有一件事让我对DI有点不安,那就是假设所有注入的对象都很容易实例化,而且不会产生副作用- or -依赖关系被频繁使用,以至于它超过了任何相关的实例化成本。
当依赖项在消费类中不经常使用时,这一点可能很重要;比如IExceptionLogHandlerService。显然,这样的服务很少在类中调用(希望:))——大概只在需要记录异常时调用;然而,规范的构造器-注入模式……
Public Class MyClass
Private ReadOnly mExLogHandlerService As IExceptionLogHandlerService
Public Sub New(exLogHandlerService As IExceptionLogHandlerService)
Me.mExLogHandlerService = exLogHandlerService
End Sub
' ...
End Class
...要求提供该服务的“活动”实例,该死的成本/副作用。并不是说它可能会这样做,但是如果构建这个依赖实例涉及到服务/数据库命中,或配置文件查找,或在释放之前锁定资源,该怎么办呢?如果该服务是按需构建的、服务定位的或工厂生成的(它们都有各自的问题),那么您将只在必要时才承担构建成本。
Now, it is a generally accepted software design principle that constructing an object is cheap and doesn't produce side-effects. And while that's a nice notion, it isn't always the case. Using typical constructor-injection however basically demands that this is the case. Meaning when you create an implementation of a dependency, you have to design it with DI in mind. Maybe you would have made object-construction more costly to obtain benefits elsewhere, but if this implementation is going to be injected, it will likely force you to reconsider that design.
顺便说一下,某些技术可以通过允许延迟加载注入的依赖项来缓解这个问题,例如,提供一个Lazy<IService>实例类作为依赖项。这将改变依赖对象的构造函数,并使其更加了解实现细节,例如对象构造开销,这也可以说是不可取的。
这是我自己的第一反应:基本上任何模式都有同样的缺点。
学习是需要时间的 如果误解了,就会弊大于利 如果走到极端,工作量可能会超过收益
如果您有一个自己开发的解决方案,依赖项就会在构造函数中直接出现。或者作为方法参数,这也不难发现。尽管框架管理的依赖关系,如果走到极端,就会开始变得像魔术一样。
然而,在太多的类中有太多的依赖项是一个明显的标志,说明你的类结构搞砸了。因此,在某种程度上,依赖注入(自行开发或框架管理)可以帮助发现那些可能隐藏在暗处的突出设计问题。
为了更好地说明第二点,这里是本文的一段摘录(原始来源),我完全相信这是构建任何系统的基本问题,而不仅仅是计算机系统。
Suppose you want to design a college campus. You must delegate some of the design to the students and professors, otherwise the Physics building won't work well for the physics people. No architect knows enough about about what physics people need to do it all themselves. But you can't delegate the design of every room to its occupants, because then you'll get a giant pile of rubble. How can you distribute responsibility for design through all levels of a large hierarchy, while still maintaining consistency and harmony of overall design? This is the architectural design problem Alexander is trying to solve, but it's also a fundamental problem of computer systems development.
DI能解决这个问题吗?不。但它确实帮助你清楚地看到,如果你试图把设计每个房间的责任委托给它的居住者。
当您不断地使用技术来处理静态类型时,静态类型语言的假定好处似乎大大减少了。我刚刚采访的一家大型Java商店正在用静态代码分析绘制他们的构建依赖关系……它必须解析所有的Spring文件才能有效。