我试图在工作中引入依赖注入(DI)模式,我们的一位主要开发人员想知道:如果有的话,使用依赖注入模式的缺点是什么?
注意,如果可能的话,我在这里寻找一个详尽的列表,而不是关于这个主题的主观讨论。
澄清:我谈论的是依赖注入模式(参见Martin Fowler的这篇文章),而不是特定的框架,无论是基于xml的(如Spring)还是基于代码的(如Guice),还是“自滚动”的框架。
Reddit的subreddit /r/programming上正在进行一些深入的讨论/咆哮/辩论。
我试图在工作中引入依赖注入(DI)模式,我们的一位主要开发人员想知道:如果有的话,使用依赖注入模式的缺点是什么?
注意,如果可能的话,我在这里寻找一个详尽的列表,而不是关于这个主题的主观讨论。
澄清:我谈论的是依赖注入模式(参见Martin Fowler的这篇文章),而不是特定的框架,无论是基于xml的(如Spring)还是基于代码的(如Guice),还是“自滚动”的框架。
Reddit的subreddit /r/programming上正在进行一些深入的讨论/咆哮/辩论。
当前回答
依赖注入是一种技术或模式,与任何框架无关。您可以手动连接依赖项。DI帮助您实现SR(单一职责)和SoC(关注点分离)。DI会带来更好的设计。从我的观点和经验来看,没有坏处。就像任何其他模式一样,你可能会弄错或误用它(但在DI的情况下很难)。
如果您使用框架将DI作为原则引入到遗留应用程序中,那么您可能犯的最大错误就是将其误用为服务定位器。DI+框架本身是伟大的,只是让事情变得更好,我看到它!从组织的角度来看,每一个新的过程、技术、模式……都有共同的问题:
你必须训练你的团队 您必须更改您的应用程序(这包括风险)
一般来说,你必须投入时间和金钱,除此之外,没有任何负面影响,真的!
其他回答
我不认为存在这样的列表,但是试着阅读这些文章:
DI会模糊代码(如果你没有使用好的IDE) Bob叔叔认为,误用IoC会导致糟糕的代码。 需要注意过度设计和创造不必要的多功能性。
当您不断地使用技术来处理静态类型时,静态类型语言的假定好处似乎大大减少了。我刚刚采访的一家大型Java商店正在用静态代码分析绘制他们的构建依赖关系……它必须解析所有的Spring文件才能有效。
如果您有一个自己开发的解决方案,依赖项就会在构造函数中直接出现。或者作为方法参数,这也不难发现。尽管框架管理的依赖关系,如果走到极端,就会开始变得像魔术一样。
然而,在太多的类中有太多的依赖项是一个明显的标志,说明你的类结构搞砸了。因此,在某种程度上,依赖注入(自行开发或框架管理)可以帮助发现那些可能隐藏在暗处的突出设计问题。
为了更好地说明第二点,这里是本文的一段摘录(原始来源),我完全相信这是构建任何系统的基本问题,而不仅仅是计算机系统。
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能解决这个问题吗?不。但它确实帮助你清楚地看到,如果你试图把设计每个房间的责任委托给它的居住者。
有一件事让我对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>实例类作为依赖项。这将改变依赖对象的构造函数,并使其更加了解实现细节,例如对象构造开销,这也可以说是不可取的。
以下几点:
DI增加了复杂性,通常是通过增加类的数量,因为责任分离得更多,这并不总是有益的 您的代码将(在某种程度上)耦合到您使用的依赖注入框架(或者更一般地说,如何决定实现DI模式) 执行类型解析的DI容器或方法通常会导致轻微的运行时损失(非常可以忽略不计,但它确实存在)
通常,解耦的好处是使每个任务更易于阅读和理解,但增加了编排更复杂任务的复杂性。