依赖注入:我应该使用框架吗?


24

我最近在一个Python项目中工作,我们在其中进行了大量依赖注入(因为必须使该应用程序可测试),但是我们没有使用任何框架。有时手动连接所有依赖项可能有点乏味,但总体来说效果很好。

当必须在多个位置创建对象时,我们只需一个函数(有时是该对象的类方法)即可创建生产实例。每当我们需要该对象时,都会调用此函数。

在测试中,我们做同样的事情:如果多次需要创建对象的“测试版本”(即由模拟对象支持的真实实例),我们就有一个函数来创建此“测试版本”类,用于测试。

就像我说的那样,总体来说效果很好。

我现在正在进入一个新的Java项目,并且我们正在决定是使用DI框架还是手动进行DI(例如上一个项目)。

请说明使用DI框架的工作方式会有所不同,以及以什么方式比手动“原始”依赖项注入更好或更坏。

编辑:我也想补充一个问题:您是否亲眼目睹了没有框架的手动执行DI的任何“专业级”项目?


在我的答案旁边。两种方式都兼容。您可以将软依赖注入留给框架,并使工厂保留关键组件。与模型或业务相关的组件。
2016年

4
我猜您已经检查过了,以防万一您为什么不这样做?为什么我们需要用于DI的框架?
2016年

Answers:


19

DI容器的关键是抽象。DI容器为您抽象了此设计问题。您可能会猜到,它对代码有显着影响,通常会转化为更少的构造函数,设置方法,工厂和构建程序。结果,它们使您的代码更加松散地耦合(由于底层的IoC),更加简洁。虽然并非没有代价。

背景资料

多年前,这种抽象要求我们编写各种配置文件(声明性编程)。看到LOC数量显着减少是很奇妙的,但是当LOCS减少时,配置文件的数量略有增加。对于中小型项目,这根本不是问题,但是对于大型项目,将“代码汇编”分散在代码和XML描述符之间的50%成为一个难题。范式本身。这是非常不灵活的,因为描述符为定制保留了很少的空间。

范式逐渐向配置约定转变,该约定将配置文件交换为注释或属性。它使我们的代码更干净,更简单,同时为我们提供了XML所无法提供的灵活性。

到目前为止,这可能是您工作方式上最重要的区别。更少的代码,更多的魔力。

在不利的一面...

约定超过配置使抽象变得如此繁重,以至于您几乎不知道它是如何工作的。有时似乎很神奇,这又带来了另一个缺点。一旦开始工作,许多开发人员就不在乎它如何工作。

对于那些使用DI容器学习DI的人来说,这是一个障碍。他们没有完全理解容器抽象的设计模式,良好实践和原则的相关性。我问开发人员是否熟悉DI及其优势,他们回答:- 是的,我使用过Spring IoC-。(这是什么意思?!?)

人们可以同意还是不同意这些“缺点”。以我的拙见,必须知道您的项目中正在发生什么。否则,我们对此将不完全了解。还要了解DI的用途,并具有如何实现DI的概念。

从积极的一面...

一句话生产力。框架让我们专注于真正重要的事情。应用程序的业务。

尽管存在很多缺点,但对于我们中的许多人(该工作取决于截止日期和成本),这些工具是宝贵的资源。我认为,这应该是我们支持实现DI容器的主要论据。生产力

测试中

无论是实现纯DI还是容器,测试都不会成为问题。恰恰相反。相同的容器通常为我们提供开箱即用的单元测试模拟。这些年来,我一直将测试用作温度计。他们告诉我我是否严重依赖集装箱设施。我之所以这样说,是因为这些容器可以初始化私有属性,而无需使用setter或构造函数。他们几乎可以在任何地方注入组件!

听起来很诱人吗?小心!不要掉进陷阱!

意见建议

如果您决定实施容器,我强烈建议您坚持良好做法。继续实现构造函数,setter和接口。强制框架/容器使用它们。这将使迁移到另一个框架或删除实际框架变得更加容易。它可以大大减少对容器的依赖。

关于这种做法:

何时使用DI容器

我的回答将因自己的经验而有偏差,主要是支持DI容器(正如我所评论的那样,我非常专注于生产力,因此我从不需要纯DI。相反)。

我认为您会发现Mark Seeman的有趣文章,也回答了有关如何实现纯DI的问题

最后,如果我们谈论Java,那么我将首先考虑一下JSR330-Dependency Injection

总结

好处

  • 削减成本和节省时间。生产率。
  • 组件数量更少。
  • 代码变得更加简单和简洁。

缺点

  • DI被委托给第三方库。我们应该意识到它们的取舍,优势和劣势。
  • 学习曲线。
  • 他们很快会让您忘记一些良好的习惯。
  • 增加项目的依赖关系(更多库)
  • 基于反射的容器需要更多的资源(内存)。如果我们受到资源的限制,这很重要。

与纯DI的区别

  • 更少的代码,更多的配置文件或注释。

1
“这些框架并不是您进行单元测试或完整性测试的专用工具,因此您不必担心测试。”这到底是什么意思?这个措辞使我认为这是一个错字,但是我在解码它时遇到了麻烦。
candied_orange

这意味着艰苦的调试或测试框架的使用不足以放弃使用这些框架的缺点。因为即使您不喜欢它们,它们仍然具有真正值得尝试的好处。最大的缺点不是框架本身。是您如何使用它们。即使您的框架不需要,也需要一些好的做法,例如接口和设置器。最终,有很好的测试库可以
轻松地

如果这是您的意思,请考虑:“这些框架不会阻止您执行单元测试或完整性测试,因此您不必担心它们如何影响测试”
candied_orange

不好意思 我试图说,即使使用这些框架,他也将能够测试他的代码。尽管是抽象的,测试仍然可行。随时编辑我的答案。如您所见,我的英语水平仍然还不错:-)
Laiv

1
请注意,Dagger2的DI稍有不同。粘合代码是在编译时作为正常的,可读的Java源代码生成的,因此遵循事物如何粘合在一起的方式要简单得多,而不必处理运行时的魔术。
托尔比约恩Ravn的安徒生

10

以我的经验,依赖项注入框架会导致许多问题。其中一些问题将取决于框架和工具。可能有一些缓解问题的方法。但这是我遇到的问题。

非全局对象

在某些情况下,您想注入一个全局对象:在运行的应用程序中只有一个实例的对象。这可能是MarkdownToHtmlRendererer,a DatabaseConnectionPool,api服务器的地址等。在这种情况下,依赖项注入框架的工作非常简单,只需将所需的依赖项添加到构造函数中就可以了。

但是那些不是全局的对象呢?如果您需要用户当前的请求怎么办?如果您需要当前活动的数据库事务怎么办?如果您需要与div相对应的HTML元素,该元素包含一个表单,该表单中的文本框刚刚触发了一个事件,该怎么办?

我看到的DI框架采用的解决方案是分层注入器。但这使注入模型更加复杂。很难弄清楚将从层次结构的哪一层注入什么。很难确定每个应用程序,每个用户,每个请求等是否存在给定对象。您不再只能添加依赖项并使它起作用。

图书馆

通常,您将需要具有可重用代码的库。这还将包括有关如何从该代码构造对象的说明。如果使用依赖项注入框架,则通常将包括绑定规则。如果要使用该库,可以将其绑定规则添加到您的绑定规则中。

但是,可能导致各种问题。您可能最终得到了绑定到不同实现的相同类。该库可能期望存在某些绑定。该库可能会覆盖一些默认绑定,从而导致您的代码执行奇怪的操作。您的代码可能会覆盖某些默认绑定,从而导致库代码执行奇怪的操作。

隐藏的复杂性

手动编写工厂时,您会感觉到应用程序的依赖关系如何组合在一起。当您使用依赖项注入框架时,您不会看到此信息。取而代之的是,对象趋于越来越依赖,直到迟早一切都几乎取决于一切。

IDE支持

您的IDE可能不会理解依赖项注入框架。使用手动工厂,您可以找到构造函数的所有调用方,以找出可能构造该对象的所有位置。但是它将找不到DI框架生成的调用。

结论

我们从依赖注入框架中得到什么?我们避免使用一些实例化对象的简单样板。我全心全意消除简单的样板。但是,维护简单的样板很容易。如果要替换该样板,我们应该坚持用更简单的方法替换它。由于我在上文中讨论过各种问题,因此我认为框架(至少目前如此)并不适合。


这是一个很好的答案,但我无法想象为类库使用IoC容器。对我来说,这感觉像是糟糕的设计。我希望一个库能够让我明确为其提供所需的任何依赖关系。
RubberDuck

@RubberDuck,就我而言,我在一家大型公司工作,该公司拥有大量的Java项目,这些项目均在同一依赖项注入框架上进行了标准化。那时,某些团队很想导出期望由DI框架组合在一起的库,而不是提供一个明智的外部接口。
温斯顿·埃韦特

不幸的是,@ WinstonEwert。感谢您为我填写上下文。
RubberDuck

与注入部分框架的运行时绑定相对于手动注入的设计时类型绑定,这对IDE部分来说非常有用。手动进行编译时,编译器可防止被忘记的注射。
Basilevs

的IntelliJ了解CDI(Java EE的标准DI)
托尔比约恩Ravn的安徒生

3

是否需要Java +依赖注入=框架?

没有。

DI框架能给我带来什么?

对象构造以非Java语言发生。


如果工厂方法足以满足您的需求,则可以在Java框架中完全免费使用100%真实的pojo java中的DI。如果您的需求不止于此,您还有其他选择。

“四人帮”设计模式可以完成许多伟大的工作,但是在创建模式方面总是存在弱点。Java本身在这里有一些弱点。DI框架确实可以比实际注入更多地帮助解决此创新弱点。您已经知道没有他们怎么做。它们将为您带来多少好处,取决于您在对象构造方面需要多少帮助。

四人帮之后出现了许多创造模式。Josh Bloch构建器是围绕Java缺少命名参数的绝妙技巧。除此之外,iDSL构建器还提供了很大的灵活性。但是,这些都代表着重要的工作和样板。

框架可以使您免于沉迷其中。他们并非没有缺点。如果您不小心,可能会发现自己依赖于框架。使用完一个框架后尝试切换框架,然后自己看看。即使您以某种方式保持了xml或注解的通用性,您最终也可以支持在广告编程工作时必须同时提及的两种基本语言。

在您过于迷恋DI框架的功能之前,需要花一些时间并研究其他可以为您提供功能的框架,否则您可能会认为自己需要DI框架。许多已经超出了简单的DI的范围。考虑:LombokAspectJslf4J。每个框架都与某些DI框架重叠,但是每个框架都可以正常工作。

如果测试确实是其背后的推动力,那么在开始考虑DI框架应该接管您的生活之前,请研究一下:jUnit (实际上是任何xUnit)Mockito (实际上是任何模拟)

您可以做自己喜欢的事情,但是对于认真的工作,我宁愿使用人口稠密的工具箱,而不是瑞士军刀。希望画出这些线比较容易,但是有些人已经在努力使它们模糊。没什么错,但是它会使这些选择具有挑战性。


PowerMock解决了其中一些情况。至少可以模拟静态
Laiv

嗯 如果您正确执行DI,将一个IoC容器框架换成另一个IoC容器框架应该相对简单。将这些注释保留在您的代码之外,您会没事的。基本上,使用IoC容器执行DI,而不是将容器用作服务定位器。
RubberDuck

@RubberDuck正确。缺少在多个DI框架下不断测试和维护代码库的知识,您知道一种简单可靠的方法来检查“您是否在正确执行DI”吗?即使我在Java中只使用pojo,也没有做任何事情,但是在XML依赖IoC容器的情况下,即使投入了大量精力来制作XML,也仍然不是简单的交换。
candied_orange

@CandiedOrange我使用“简单”作为相对术语。在宏伟的计划中,用一个合成词根替换另一个合成词根并不是一项艰巨的任务。几天的工作时间到了。为了确保您正确执行DI,这就是代码审查的目的。
RubberDuck

2
@RubberDuck我见过的大多数“组合根”在main中大约有5行代码。所以不行。这不是一项艰巨的任务。这是专有内容,可以避开我警告的那些行。您称其为“正确执行DI”。我问你是否会用什么来证明代码审查中的问题。还是只说“ Meh”?
candied_orange

2

创建类并为其分配依赖项是建模过程的一部分,这是有趣的部分。这就是大多数程序员喜欢编程的原因。

确定将使用哪种实现以及如何构造类是必须以某种方式实现的事情,并且是应用程序配置的一部分。

手动编写工厂是一项繁琐的工作。DI框架通过自动解决可能解决的依赖关系并为那些可能无法解决的依赖关系进行配置,从而使其变得更加容易。

使用现代IDE,您可以浏览方法及其用法。拥有手动编写的工厂非常好,因为您可以仅突出显示类的构造函数,显示其用法,并且它将向您显示构造特定类的所有位置和方式。

但是,即使有了DI框架,您也可以拥有一个中心位置,例如对象图构造配置(从现在开始是OGCC),如果一个类依赖于模棱两可的依赖关系,则只需查看OGCC并查看如何构造特定的类即可。在那里。

您可能会反对,您个人认为能够查看特定构造函数的用法是一个不错的选择。我想说的是,对您来说更好的不仅是主观的,而且主要来自个人经验。

如果您一直从事依赖于DI框架的项目,那么您将只知道这一点,并且当您想要查看类的配置时,您总是会研究OGCC,甚至不会尝试查看构造函数的使用方式,因为您会知道所有依赖项都会自动连接。

自然地,DI框架不能用于所有事物,并且有时您将不得不编写一两个工厂方法。一个非常常见的情况是在迭代期间在集合上构造依赖项,其中每个迭代都提供一个不同的标志,该标志应生成其他本来公共接口的不同实现。

最后,很可能全部归结为您的喜好和您愿意牺牲的(无论是编写工厂还是透明度)。


个人经历(警告:主观)

作为PHP开发人员,尽管我喜欢DI框架背后的想法,但我只使用过一次。那是我与之共事的团队当时应该非常迅速地部署应用程序的时候,我们不会浪费时间编写工厂。因此,我们只是选择了一个DI框架,对其进行了配置,并且以某种方式起作用。

对于大多数项目,我喜欢手动编写工厂,因为我不喜欢DI框架中发生的黑魔法。我是负责对类及其依赖项进行建模的人。我是一个决定为什么设计看起来如此的人。因此,我将成为正确构造类的人(即使该类具有严格的依赖关系,例如具体的类而不是接口)。


2

我个人不使用DI容器,也不喜欢它们。我有更多原因。

通常这是一个静态依赖。因此,它只是具有所有缺点的服务定位器。然后,由于静态依赖项的性质,它们被隐藏了。您不需要与它们打交道,它们已经以某种方式存在,这很危险,因为您不再控制它们。

打破了OOP原则,例如凝聚力和David West的Lego-Brick对象隐喻。

因此,要使整个对象尽可能靠近程序的入口点,这是要走的路。


1

正如您已经注意到的:根据所使用的语言,有不同的观点,不同的人将如何处理类似的问题。

关于依赖项注入,可以归结为:顾名思义,依赖项注入只不过是将一个对象需要的依赖项 放入 该对象中,而另一种方式则与此相反:让对象本身使它依赖的对象实例无效。您可以将对象创建对象消耗分离开来,以获得更大的灵活性。

如果您的设计布局合理,则通常只有几个类具有许多依赖关系,因此,通过手工或框架进行依赖关系的连接应该相对容易,编写测试的样板应该易于管理。

就是说,不需要DI框架。

但是,为什么仍然要使用框架?

I)避免样板代码

如果您的项目规模很大,您会感到一遍又一遍地编写工厂(和声明)的痛苦,那么您应该使用DI框架

II)划时代的编程方法

当Java曾经使用XML进行编程时,现在变成了用注解撒上仙尘,然后神奇地发生了

但认真的说:我认为这有明显的好处。您可以清楚地传达出意图,说出需要什么,而不是在哪里找到它以及如何构建它。


tl; dr

DI框架并不能使您的代码库变得神奇,但它有助于使美观的代码看起来清晰。在感觉需要时使用它。在您项目的某个时刻,它将节省您的时间并防止恶心。


1
你是对的!我倾向于过分担心事情的幕后运作方式。我在使用我几乎不了解的框架方面经历过糟糕的经历。我不主张测试框架,只是了解它们的作用和方式(如果可能)。但这取决于您自己的兴趣。我认为,了解它们的工作原理后,您可以更好地决定它们是否适合您。例如,以相同的方式比较不同的ORM。
Laiv

1
我认为知道事物是如何工作的总是正确的。但这不应该使您不知所措。我认为Spring的卖点是,它可以正常工作。是的,您绝对正确,为了对是否使用框架做出明智的决定,您必须具有一定程度的知识。但是我经常看到对于那些与自己不一样的伤疤的人的一种不信任感,尽管在您的情况下并非如此,只要有信仰,就只能允许那些经历过手工狩猎的人去超市。
Thomas Junk

幸运的是,这些天我们甚至不需要Java的Spring或DI框架。我们已经得到了JSR330,可悲的是它被忽略了(至少我没有看到一个实现它的项目)。因此,人们仍有时间赢得他们的伤疤:-)
Laiv

我已经编辑了答案,并考虑了您的评论。我已经删除了与调试有关的“缺点”。我得出的结论是您是正确的。看一下并考虑更新您的答案,因为现在您将引用我从答案中删除的部分。
Laiv

1
答案变化很小!!!没什么让您担心的:-)。消息和参数保持不变。我只是希望您的回答保持原样。我想只是您删除了其中一个引号。
Laiv

0

是。您可能应该阅读Mark Seemann的书,名为“依赖注入”。他概述了一些良好做法。根据您所说的内容,您可能会从中受益。您应该努力使每个工作单元具有一个合成根或一个合成根(例如,Web请求)。我喜欢他的想法,即您创建的任何对象都被视为依赖项(方法/构造函数的任何参数也是如此),因此您应该真正注意在何处以及如何创建对象。框架将帮助您使这些概念保持清晰。如果您阅读Mark的书,您不仅会找到问题的答案。


0

是的,在使用Java时,我建议使用DI框架。这有几个原因:

  • Java有几个非常出色的DI软件包,它们提供了自动的依赖注入,并且通常还附带了比简单的DI难于手动编写的附加功能(例如,有作用域的依赖项,通过生成代码来查找作用域应位于哪个作用域之间,从而实现了作用域之间的自动连接。使用d查找该范围的依赖关系的正确版本-因此,例如,应用程序范围的对象可以引用请求范围的对象)。

  • Java批注非常有用,并且提供了配置依赖项的绝佳方法

  • 与您在python中使用过的对象相比,java对象的构建过于冗长;与使用动态语言相比,在这里使用框架可以节省更多的工作。

  • Java DI框架可以做一些有用的事情,例如自动生成代理和装饰器,它们在Java中比在动态语言上工作更多。


0

如果您的项目足够小,并且您对DI框架没有太多的经验,我建议您不要使用该框架而是手动执行。

原因:我曾经在一个项目中工作过,该项目使用了两个直接依赖于不同di框架的库。他们俩都有特定于框架的代码,例如XYZ di-give-me-an-instance-of-XYZ。据我所知,一次只能有一个活动的双框架实现。

使用这样的代码,弊大于利。

如果您有更多的经验,您可能会通过接口(工厂或服务定位器)抽象化di-framwork,因此该问题将不再存在,并且di框架将带来更多的好处。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.