当前位置:  开发笔记 > 前端 > 正文

使用依赖注入有什么缺点?

如何解决《使用依赖注入有什么缺点?》经验,为你挑选了15个好方法。

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

注意我在这里寻找 - 如果可能 - 详尽的清单,而不是关于该主题的主观讨论.


澄清:我在谈论依赖注入模式(参见Martin Fowler的这篇文章),而不是一个特定的框架,无论是基于XML(如Spring)还是基于代码(如Guice),还是"自我推销" .


编辑:这里有一些很好的进一步讨论/咆哮/辩论/ r /编程.



1> Håvard S..:

几点:

DI通常通过增加类的数量来增加复杂性,因为职责分离得更多,这并不总是有益的

您的代码将(某种程度上)耦合到您使用的依赖注入框架(或者更一般地说,您决定如何实现DI模式)

DI容器或执行类型解析的方法通常会产生轻微的运行时间损失(非常微不足道,但它存在)

通常,解耦的好处使每个任务更易于阅读和理解,但增加了编排更复杂任务的复杂性.


+1"DI增加了复杂性"这在DI及其他方面都是如此.(几乎)每当我们增加灵活性时,我们就会增加复杂性.这完全取决于平衡,首先要了解上行和下行.当人们说'没有缺点'时,这肯定表明他们还没有完全理解这件事.
我们是人类; 我们的短期记忆是有限的,不能同时处理多个.对于类,方法,函数,文件或用于开发程序的任何构造,它都是相同的.
- 类的分离降低了复杂性.许多类不会使应用程序变得复杂. - 您应该只在application-root上依赖于DI框架.
@Havard S:是的,但是5个非常复杂的类并不比15个简单类简单.降低复杂性的方法是减少程序员处理代码所需的工作集 - 复杂的,高度相互依赖的类无法实现这一点.
@kyoryu我想我们都同意这一点.请记住,_complexity与coupling_不同.减少耦合会增加复杂性.我真的不是说DI是一件坏事,因为它增加了类的数量,我强调了与之相关的潜在缺点.:)
英皇酒店?我觉得每个人都知道DI错了.DI不需要框架.没有开销.此外,如果你已经有足够的解耦代码(你希望这样做!)使用DI将不再添加任何类.这就是你如何将依赖关系传递给对象.使用DI,您可以将它们传递给对象(在构造,设置器或方法上),而不是在类中进行硬编码.这不应产生开销,也不应产生复杂性 它简化和解耦.
是啊.今天我在大约五分钟内放弃了一个主要功能,因为我所要做的就是删除一些类并修改我的对象图.由于没有改变实际逻辑,因此很容易做到高可信度,并且丢失对象的编译器错误告诉我需要更改什么.男人,那种复杂性肯定让我陷入困境!
@Kissaki:谢谢!我在想*我错了DI因为我只是通过了依赖,而不是使用框架;)
基本上它降低了每个单独构建块的复杂性,但增加了用于构建墙的构建块的数量.
在@Robert所说的基础上,DI从许多类中消除了布线依赖性形式的一点复杂性并集中了它.它还使每个测试都不那么复杂.接线的新地方可能有点复杂,但整体DI应该大大降低复杂性.如果您认为DI增加了复杂性,那么您可能做错了.
你可能会认为将长方法分解为短方法会增加复杂性.
在你的问题中,你总是有那种复杂性**.在考虑到Composition的设计时,你不得不提前考虑这种复杂性,并决定现在要解决多少,要多少钱,开放多少等等.这并不是说组合(DI)是唯一的解决这个问题的方法,但最好是面对**现在**的复杂性,而不是在你意识到你模拟了一切都错误时的生产,但嘿,DB调用真的很快!

2> Steve314..:

使用面向对象编程,样式规则以及其他所有内容时,您经常会遇到同样的基本问题.事实上,很可能 - 非常普遍 - 做太多的抽象,并添加太多的间接,并且通常在错误的地方过度地应用好的技术.

您应用的每个模式或其他构造都会带来复杂性 抽象和间接散布信息,有时会将不相关的细节移开,但同样有时会更难理解究竟发生了什么.您应用的每条规则都会带来不灵活性,排除可能只是最佳方法的选项.

关键是要编写能够完成工作并且健壮,可读和可维护的代码.您是软件开发人员 - 而不是象牙塔建设者.

相关链接

http://thedailywtf.com/Articles/The_Inner-Platform_Effect.aspx

http://www.joelonsoftware.com/articles/fog0000000018.html


可能最简单的依赖注入形式(不要笑)是一个参数.依赖代码依赖于数据,并且通过传递参数来注入数据.

是的,这是愚蠢的,它并没有解决依赖注入的面向对象的点,而是一个函数式程序员会告诉你(如果你有一流的功能),这是唯一的一种依赖注入的需要.这里的重点是采取一个简单的例子,并显示潜在的问题.

让我们来看看这个简单的传统函数--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看一下这个真实世界的函数......

http://msdn.microsoft.com/en-us/library/ms632680%28v=vs.85%29.aspx

这是12个"依赖"来处理.例如,如果屏幕分辨率变得非常大,那么我们可能需要64位坐标值 - 以及另一个版本的CreateWindowEx.是的,已经有一个旧版本仍然存在,可能会被映射到幕后的新版本......

http://msdn.microsoft.com/en-us/library/ms632679%28v=vs.85%29.aspx

这些"依赖"不只是原始开发商的一个问题 - 大家谁使用此接口就需要查找的依赖关系是什么,他们是如何规定的,以及它们的含义,并找出如何为他们的应用程序中执行.这就是"明智的默认"这个词可以让生活变得更加简单.

面向对象的依赖注入原则上没有区别.在源代码文本和开发人员时间编写类都是一种开销,如果根据某些依赖对象规范编写该类来提供依赖关系,则依赖对象被锁定为支持该接口,即使有需要也是如此.替换该对象的实现.

这些都不应该被解读为声称依赖注入是坏的 - 远非如此.但是任何好的技术都可以过度应用并且在错误的地方.正如不需要提取每个字符串并将其转换为参数一样,并非每个低级行为都需要从高级对象中提取出来并转换为可注入的依赖项.


@Kissaki - 是的,它改变了你传递依赖关系的方式.而且你必须编写代码来处理依赖项的传递.在此之前,您必须确定要传递哪些依赖项,以对未编码依赖代码的人有意义的方式定义它们等.您可以避免运行时影响(例如,使用策略参数到模板在C++中),但这仍然是你必须编写和维护的代码.如果它是合理的,这是一个非常小的价格,一个大的奖励,但如果你假装没有价格支付,这意味着你将在没有收益时支付该价格.
@Kissaki - 至于缺乏灵活性,一旦你指定了你的依赖项,你就会被锁定 - 其他人根据该规范编写了依赖注入依赖项.如果你需要一个需要稍微不同依赖关系的依赖代码的新实现 - 很难,你就会被锁定.时间开始为这些依赖项编写一些适配器(以及更多的开销和另一层抽象).
依赖注入没有任何缺点.它只会改变传递依赖关系到对象的方式,这不会增加复杂性,也不会增加不灵活性.恰恰相反.
@ ingredient_15939 - 是的,这是一个简化的玩具示例.如果您真的这样做,那么您只需使用重载而不是适配器,因为复制代码的成本低于适配器的成本.但请注意,复制代码通常是一件坏事,并且使用适配器来避免重复代码是另一种好方法(有时可能会使用太多而且位置错误).

3> Epaga..:

这是我自己的初步反应:基本上与任何模式相同的缺点.

学习需要时间

如果被误解,可能会导致弊大于利

如果采取极端的做法,可能会比证明利益更合理


考虑到你不想进行主观讨论,这看起来似乎有些波动.我建议(总体上,如果必要的话)建立一个考验的现实例子.在没有*DI的情况下构建样本*将是一个很好的起点.然后,我们可以挑战需求变更并检查影响.(顺便说一下,当我准备去睡觉时,这对我来说非常方便.)
这真的需要一些例子来支持它,并教过几十个人我可以告诉你,这是一个非常简单的学习和开始实践的概念.
我并不认为DI是一种模式.它(加上IoC)实际上更像是一种编程模型 - 如果你完全遵循它们,你的代码看起来比"典型的"伪程序OO更像Actor.
为什么学习需要时间?我认为与ejb相比,它简单易行.

4> kyoryu..:

控制反转的最大"缺点"(不是DI,但足够接近)是它倾向于删除只有一个点来查看算法的概述.这基本上就是当你有解耦代码时会发生的事情 - 在一个地方寻找的能力是一种紧密耦合的神器.


这就是"下行"这个词在引号中的原因:)松散的耦合和强大的封装排除了"一个地方看到一切",根据定义.如果你觉得我反对DI/IoC,请参阅我对Havard S的回应的评论.
但肯定的是,"缺点"是因为我们正在解决的问题的性质,将它解耦以便我们可以轻松地改变实施意味着没有一个地方可以看,它能够看到它的相关性是什么?我能想到的唯一情况是在调试和调试环境应该能够进入实现.
相反,DI的一个优点是你可以查看单个类的构造函数,并立即计算出它所依赖的类(即需要哪些类来完成它的工作).这对于其他代码来说要困难得多,因为代码可以毫不费力地创建类.

5> Gabriel Ščer..:

我不认为这样的列表存在,但尝试阅读这些文章:

DI可以掩盖代码(如果你没有使用好的IDE)

根据鲍勃叔叔的说法,滥用IoC会导致代码错误.

需要注意过度工程并创建不必要的多功能性.


-1因为我不是在寻找链接集合,我正在寻找实际答案:如何总结每篇文章的观点?然后我反而投票.请注意,文章本身很有意思虽然看起来前两个实际上是揭穿DI的负面消息?
我很好奇,当有人要求获得wonsides时,为什么以"没有任何"的方式回答这些问题并且那些包含与问题相关的信息的答案不是?
我想知道最受欢迎的答案是否也得到了好评,因为它真的没有回答这个问题所有imho :)当然我知道你问的问题和我的答案,我不是要求upvotes,我只是困惑为什么我的答案是没有帮助,虽然显然说DI的缺点是它太酷了帮助很多.

6> 小智..:

在过去的6个月里,我一直在广泛使用Guice(Java DI框架).总的来说,我觉得它很棒(特别是从测试的角度来看),但也有一些缺点.最为显着地:

代码可能变得难以理解.依赖注入可以用于非常有创意的方式.例如,我遇到了一些使用自定义注释来注入某些IOStream的代码(例如:@ Server1Stream,@ Server2Stream).虽然这确实有效,但我承认它有一定的优雅,它使理解Guice注入成为理解代码的先决条件.

学习项目时学习曲线较高.这与第1点有关.为了理解使用依赖项注入的项目如何工作,您需要了解依赖项注入模式和特定框架.当我开始目前的工​​作时,我花了不少时间来研究Guice在幕后所做的事情.

构造者变得庞大.虽然这可以通过默认构造函数或工厂在很大程度上解决.

错误可能会被混淆.我最近的一个例子是我在两个旗帜名称上发生了碰撞.Guice默默地吞下了这个错误,我的一个标志没有被初始化.

错误被推送到运行时.如果您错误地配置Guice模块(循环引用,错误绑定......),则在编译期间不会发现大多数错误.相反,当程序实际运行时会暴露错误.

现在我已经抱怨了.让我说我会继续(心甘情愿地)在我目前的项目中使用Guice,而且很可能是我的下一个.依赖注入是一种伟大且极其强大的模式.但它肯定会令人困惑,你几乎肯定会花一些时间诅咒你选择的任何依赖注入框架.

此外,我同意其他海报,依赖注入可能被滥用.


我不明白为什么人们没有做出更大的"错误被推向运行时"对我来说这是一个交易破坏者,静态类型和编译时错误是给予开发人员的最大礼物,我不会扔他们离开了什么
@RichardTingle,IMO在应用程序启动期间DI模块将首先初始化,因此一旦应用程序启动而不是在几天或几天后,模块中的任何错误配置都将可见.也可以逐步加载模块,但如果我们在初始化应用程序逻辑之前通过限制模块加载来坚持DI的精神,我们就可以成功地将错误的绑定隔离到应用程序的开头.但是如果我们将它配置为服务定位器反模式,那么那些不良绑定肯定会让人感到惊讶.

7> Anthony..:

没有任何DI的代码会遇到众所周知的陷入Spaghetti代码的风险- 一些症状是类和方法太大,做得太多而且不容易改变,分解,重构或测试.

使用DI的代码很多可以是Ravioli代码,其中每个小类就像一个单独的馄饨块 - 它做了一件小事,坚持单一责任原则,这很好.但是看着自己的课程很难看出整个系统的作用,因为这取决于所有这些小部件如何组合在一起,这很难看出来.它看起来像一大堆小东西.

通过避免大类中大量耦合代码的意大利面条复杂性,您冒着另一种复杂性的风险,其中存在许多简单的小类,并且它们之间的交互是复杂的.

我不认为这是一个致命的缺点 - DI仍然非常值得.一定程度的馄饨风格与小班只做一件事可能是好的.即使过量,我认为它不像意大利面条代码那么糟糕.但是要意识到它可以走得太远是避免它的第一步.按照链接讨论如何避免它.



8> Anurag..:

如果您有自己开发的解决方案,那么依赖关系就在构造函数中.或者也许作为方法参数再次不难发现.虽然框架管理的依赖项,如果采取极端,可以开始像魔术一样.

但是,在太多类中有太多依赖关系是一个明显的迹象,表明你的类结构被搞砸了.所以在某种程度上,依赖注入(本土或框架管理)可以帮助带来明显的设计问题,否则可能隐藏在黑暗中潜伏.


为了更好地说明第二点,这里是本文的摘录(原始来源),我完全相信这是构建任何系统的基本问题,而不仅仅是计算机系统.

假设你想设计一个大学校园.你必须将一些设计委托给学生和教授,否则物理学建筑将不适合物理学家.没有一个建筑师能够充分了解物理学家需要做什么.但是你不能将每个房间的设计委托给它的居住者,因为那样你就会得到一堆巨大的碎石.

如何在大型层次结构的各个层面分配设计责任,同时仍保持整体设计的一致性和和谐性?这是亚历山大试图解决的架构设计问题,但它也是计算机系统开发的基本问题.

DI解决了这个问题吗?.但它确实可以帮助您清楚地了解您是否正在尝试将设计每个房间的责任委托给其居住者.



9> ckittel..:

让我用DI稍微蠕动的一件事是假设所有注入的对象都很便宜实例化并且不产生副作用 - 或者 - 依赖性被频繁使用以至于它超过任何相关的实例化成本.

这可能是重要的,因为在消费类中不经常使用依赖; 比如像IExceptionLogHandlerService.显然,类似的服务在类中很少被调用(希望:)) - 大概只是需要记录的异常; 然而规范的构造函数 - 注入模式 ......

Public Class MyClass
    Private ReadOnly mExLogHandlerService As IExceptionLogHandlerService

    Public Sub New(exLogHandlerService As IExceptionLogHandlerService)
        Me.mExLogHandlerService = exLogHandlerService
    End Sub

     ...
End Class

......要求提供此服务的"实时"实例,谴责实现该服务所需的成本/副作用.并非它可能会,但如果构建此依赖项实例涉及服务/数据库命中,或配置文件查找,或锁定资源直到处置?如果这个服务是根据需要,服务定位或工厂生成(所有都有自己的问题)构建的,那么您将仅在必要时承担建设成本.

现在,这是一种普遍接受的软件设计原则,即构造对象便宜又不会产生副作用.虽然这是一个很好的概念,但并非总是如此.然而,使用典型的构造函数注入基本上要求就是这种情况.在创建依赖项的实现时,您必须考虑到DI而设计它.也许你会让对象构建在其他地方获得更多的成本变得更加昂贵,但是如果要注入这种实现,它可能会迫使你重新考虑这种设计.

顺便说一下,某些技术可以通过允许延迟加载注入的依赖项来缓解这个确切的问题,例如提供一个类Lazy实例作为依赖项.这将改变你的依赖对象的构造函数,然后更加认识到实现细节,例如对象构造开销,这也可能是不可取的.



10> 小智..:

你只是通过实现依赖注入而没有ACTUALLY解耦它来解耦你的代码的错觉.我认为这是DI最危险的事情.



11> Jack Leow..:

这更像是一种挑剔.但依赖注入的一个缺点是它使开发工具更难以推理和导航代码.

具体来说,如果您在代码中按Control-Click/Command-Click方法调用,它将转到接口上的方法声明而不是具体实现.

这实际上是松散耦合代码(由接口设计的代码)的缺点,即使您不使用依赖注入(即使您只是使用工厂)也适用.但依赖注入的出现真正鼓励松散耦合的代码到群众,所以我想我会提到它.

此外,松散耦合代码的好处远远超过这个,因此我称之为挑剔.虽然我已经工作了很长时间才知道如果你试图引入依赖注入,这可能会得到这种推迟.

事实上,我冒昧地猜测,对于依赖注入你可以找到的每一个"缺点",你会发现许多优势远远超过它.


使用resharper的Ctrl-shift-B将带您实现

12> sanityinc..:

基于构造函数的依赖注入(没有神奇的"框架"的帮助)是构建OO代码的一种干净且有益的方式.在我见过的最好的代码库中,多年来与Martin Fowler的其他前同事一起度过,我开始注意到大多数以这种方式编写的优秀类最终只有一种doSomething方法.

那么,主要的缺点是,一旦你意识到它只是一个长期的OO编写闭包的方式,为了获得函数式编程的好处,你编写OO代码的动机很快就会消失.



13> James B..:

我发现构造函数注入可能导致大的丑陋构造函数,(我在整个代码库中使用它 - 也许我的对象太粒度了?).此外,有时使用构造函数注入时,我最终会遇到可怕的循环依赖(尽管这种情况非常罕见),因此您可能会发现自己必须在更复杂的系统中进行多轮依赖注入的状态生命周期.

但是,我赞成construtor注入而不是setter注入,因为一旦我的对象被构​​造,那么我毫无疑问地知道它处于什么状态,无论是在单元测试环境中还是在一些IOC容器中加载.其中,以一种迂回的方式,说出我认为塞特尔注射的主要缺点.

(作为旁注,我确实发现整个主题非常"宗教",但你的里程会因你的开发团队的技术狂热程度而有所不同!)


如果你有一些丑陋的构造函数,那么你的类可能会变大,你只需要很多依赖项?
这当然是我愿意接受的一种可能性!......可悲的是,我没有一个团队,我可以做同行评审.*小提琴在后台轻柔播放,然后屏幕逐渐消失为黑色...*

14> Mike Post..:

如果您在没有IOC容器的情况下使用DI,那么最大的缺点是您可以快速了解代码实际拥有的依赖关系以及所有内容的紧密耦合程度.("但我认为这是一个很好的设计!")自然的进展是走向一个IOC容器,这可能需要一点时间来学习和实现(不像WPF学习曲线差,但它不是免费的其一).最后的缺点是一些开发人员将开始写好诚实的单元测试,它将花费时间来弄明白.以前可以在半天内解决问题的开发人员会突然花两天的时间试图弄清楚如何模拟他们所有的依赖关系.

与Mark Seemann的答案类似,最重要的是你花时间成为一个更好的开发人员,而不是将一些代码混杂起来并将其抛到门外/投入生产.你的企业会选择哪个?只有你能回答这个问题.



15> Robert..:

DI是一种技术或模式,与任何框架无关.您可以手动连接依赖项.DI帮助您使用SR(单一责任)和SoC(关注点分离).DI导致更好的设计.从我的观点和经验来看,没有任何缺点.与任何其他模式一样,你可能会错误或误用它(但在DI的情况下相当困难).

如果您将DI作为原则引入遗留应用程序,使用框架 - 您可以做的最大的错误就是将其误用为服务定位器.DI + Framework本身很棒,只要我看到它,就会让事情变得更好!从组织的角度来看,每个新流程,技术,模式都有共同的问题......:

你必须训练你的团队

您必须更改您的申请(包括风险)

一般来说,你必须投入时间和金钱,除此之外,真的没有缺点!

推荐阅读
mobiledu2402851173
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有