何时不使用IoC和DI?

36
我看到很多文章都在说IoC和DI有多么好,但没有人讲为什么它并不那么出色,因为它可能会使代码更加复杂。我也看到过IoC不应该放在核心代码中,而应该用于库和插件。文章通常只对这两种模式如何使代码更加复杂做了简单的提及,但没有详细说明。这就是我的问题——在哪些特定情况下不应使用这些模式?
这是一个不错的帖子:什么是控制反转?。如果你往下看,会发现有一个关于拼写检查器的帖子以及另一个关于如果只有一个拼写检查器,那么IoC可能不是一个好的选择的帖子。作为一般准则,如果接口只有一个具体类实现(如IMyClass只有一个名为MyClassA的实现),那么是否就不应该使用IoC呢?为什么我需要在那里使用IoC呢?
如果我有MyClassA、MyClassB和MyClassC,它们各自实现IMyClass,那么它们很可能是IoC的良好候选对象,是吗?
从同一个帖子中,有人知道这篇帖子的含义吗:
  • 控制反转=婚姻
  • IoC容器=妻子

如果你还没有阅读过的话,我也推荐马丁·福勒的IoC/DI文章:http://martinfowler.com/articles/injection.html - Brent Writes Code
3
我已经阅读过它,但对我的问题没有用。 - 4thSpace
也许“婚姻/妻子”的类比是“妻子是婚姻的一个具体实现”? :) 有各种不同的IOC容器,但控制反转是一个概念。 - Prokurors
8个回答

35
关于您关于仅有一个接口实现的问题。在使用IoC时,仍然建议使用接口,这将使得创建真正的单元测试变得更加容易(不需要依赖于接口实现来正确工作),并使用这些接口的模拟对象。使用IoC的核心是使代码更容易测试。因此,如果您不想进行测试或已经有了更好的测试计划,请勿使用IoC。
个人认为,DI和IoC增加的复杂性通过更容易测试和减少耦合的代码而得到回报。这样更容易隔离问题并进行未来更改。您还可以更好地控制其行为。
我可以看出在哪种情况下不使用IoC容器(因为它会产生配置开销)。这将在小型项目中发生,您可以手动完成而不是使用容器。但是,除非您不打算测试您的代码,否则我无法看到使用DI所带来的损失...

2
改进设计重构难道不能消除对IoC和DI的需求吗?我这么问是因为IoC和DI并不具有表达性(即可读性)。如果代码可以重构以提高可读性和IoC所追求的独立性,我相信你最终会得到更好的解决方案。但是,如果不使用IoC和DI,可能仍然无法满足模拟测试的要求。 - 4thSpace
3
在我看来,控制反转(IoC)和依赖注入(DI)可以增加代码的可读性,一旦知道对象需要什么。如果你的构造函数参数太多,可能意味着该类内部功能过于复杂,需要进行重构。 - Samuel Carrijo
此外,Java以外的大多数编程语言都有“命名初始化参数”,进一步减少了DI风格可能带来的可读性问题。 - jrockway

21

控制反转(Inversion of Control)= 婚姻

IOC容器(IOC Container)= 妻子

婚姻是一个已知模式的定义 - 妻子是该模式的实现 ;-)

Bernard。


15
这不是非常准确的说法。IoC容器就像一位牧师,将两个对象(人)结合在一起。 - Kugel

16

在个别类层面上,是否使用IoC容器并不是一个决定 - 在任何项目中,您都会有被容器创建和未被容器创建的类型。

这里的复杂度权衡为:

  • 对于少量组件,IoC容器会增加一些开销
  • 随着应用程序中组件数量的增加:
    • 如果没有IoC容器,则添加新组件或重构往往变得越来越困难
    • 如果使用IoC容器,则添加新组件或重构保持不变:您只需要关注要添加或更改的组件的直接依赖项。

如果您曾经经历过即使是设计良好且维护良好的代码库随着规模变得难以管理的现象,那么您就已经体验到了IoC容器解决的问题。


刚刚发现自己处于这种情况,我正在问这个确切的问题,因为我无法看到在这种特定情况下使用我的IoC容器(它是AutoFac,所以谢谢!:)有任何好处,而在项目的其他部分中,IoC似乎是一个明显的选择,因为好处是清晰的。 - si618

11

来自Castle Project

为什么不使用它?

如果您对IoC容器的概念不熟悉,或者不了解它们试图解决的问题,那么您不应该使用它。

此外,根据项目的规模和复杂程度,IoC容器可能过于繁琐。建议在中大型项目中使用。


3
-1 "如果你不熟悉相关概念,也没有意识到它们试图解决的问题,那么你不应该使用???" 这是一个重言论,适用于任何???。 +1 第二个原因是好的。 - flybywire
#1 实际上更多的是常识,一个简单而真诚的“你知道自己在做什么吗?”的问题就是你可以进行的最简单的代码审查。 - Austin Salonen
14
小应用程序往往会变得越来越大。如果您将小应用程序像小应用程序一样编写,并且它变成了大型应用程序,您会遇到混乱。如果您将小应用程序像大型应用程序一样编写,并且它变成了大型应用程序,一切都可以正常运行。唯一的缺点是不得不听人们抱怨“过度设计”。 - jrockway
@jrockway -- 这也是我的经验。一个好的IoC容器的好处在于,一个大项目看起来就像是小项目一样简单。 - Austin Salonen
这是一个旁问:静态类是否需要使用IoC或ID? - 4thSpace
2
@4thSpace -- 我会说不,但你应该小心使用静态类。它们变得“方便”,然后成为上帝对象,然后无法测试,最终成为问题的主要来源。 - Austin Salonen

6

人们对IoC的抱怨很容易理解:IoC将简单的事情变得复杂了。假设你想对列表中的每个元素执行某些操作(伪代码):

for each i in list:
    do_something(i)

控制反转(IoC)本质上是将循环迭代的责任移交给其他人。因此,我们最终得到的是类似于以下内容:

ioc = new IocContainer()
ioc.register_callback(do_something)
ioc.go()

请注意,即使是这个简单的形式,代码也比原来的代码更长...而且这还没有计算IocContainer的实现。
然后,在其最复杂的形式中,IocContainer可以通过静态初始化或静态Main()函数或其他地方进行初始化,并根据某些迭代遍历XML文件的其他代码自动调用register_callback()函数,该文件列出了所有要做的do_something()函数以及(有时)甚至是要迭代的列表内容。
耶,XML文件使您的软件更具可配置性! 对吧? 您只需更改简单的文本文件即可更改要对列表执行的操作!
除了具有讽刺意味的是,您的源代码现在更长,更难理解,它依赖于各种新的、可能存在缺陷的库(包括IocContainer和XML解析器),并且您实际上使其更难维护:XML代码比原始简单的源代码循环(无论循环使用哪种语言编写)更难理解和编辑。 您还对如何执行迭代(排序还是未排序?深度优先还是广度优先?)的方式控制较少,因为IocContainer正在夺走这一责任。
对于像插件这样的东西,使用IoC可能是有意义的,因为主应用程序控制执行过程是合乎逻辑的,并且只需在这里和那里向插件请求帮助。 这称为“提供钩子”,比IoC术语更早出现。
对于像单元测试这样的事情(我通常在这里看到它被抛弃),IoC通常是无用的,因为您的测试需要能够模拟各种奇怪的情况,而IoC只会不断地妨碍这一点。
IoC的基本假设是加载数据并循环遍历某种方式很困难,需要进行抽象化;这个假设并不正确,因为它从来没有超过几行(或者您可以将其移动到一个单独的函数中),即使它确实节省了代码,也会降低您的灵活性。

4
我不会写一篇长篇文章来驳斥这个观点,但基本上我不同意。你的第一个例子是正交的;“对于列表中的每个i”应该重写为Functor上的fmap。这与依赖注入无关。至于DI,我发现它可以更轻松地进行我的应用程序的“主要”更改。如果某个“更远”的部分需要新的数据,我就不必重新构造大量代码“在中间”。它让我的对象图建立者处理细节,并使我的代码更加模块化。两者都好! - jrockway
"...我的对象图构建器..." 在.NET中有一个好的对象图示例吗? - 4thSpace

4

控制反转(IoC)和依赖注入(DI)不是技术,而是范例。你的问题类似于问“何时不应使用面向对象编程?”这是一个比代码库中的单个单位更大的决策。

回答你具体的问题,如果你的类很少,可以通过手动构建对象图来合理地构建对象图,并且不会多次重复相同的对象图实例化,则可能不需要IoC容器。


你能解释一下对象图吗? - 4thSpace
对象图描述了运行时实例之间的关系。例如,假设FooService具有一个IBarRepository作为构造函数依赖项,并且您使用BarRepository的实例来满足它。那么对象图将是FooService0 -> BarRepository0。由于IoC容器满足所有依赖关系,因此可以说它构建了对象图。 - Bryan Watts

2

我想说的是,只有你希望进行单元测试的对象才需要使用IoC。

如果这个回答显得轻率,我很抱歉,因为我对那些根本没有考虑过单元测试的代码感到沮丧。我真的不明白为什么要增加复杂度——如果我确实需要确定实际对象类型,我总是可以在运行时打印出类。


0
本文回答了你的问题“在哪些情况下不应该使用这些模式?”并详细说明了你的评论“这两种模式([IoC和DI])可能会使代码更加复杂”的例子:
请参阅http://java.dzone.com/articles/beyond-injection-concerns-ioc
总结本文的两个要点:
1. 在封装很重要的情况下,不应使用IoC/DI。
2. 在无法演示通过使用IoC/DI增加的复杂性超过使用IoC/DI的好处的具体示例时,不应使用IoC/DI。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接