已经发布了几个关于依赖注入的具体问题,例如何时使用它以及它有什么框架,
什么是依赖注入,何时/为什么应该或不应该使用它?
已经发布了几个关于依赖注入的具体问题,例如何时使用它以及它有什么框架,
什么是依赖注入,何时/为什么应该或不应该使用它?
当前回答
我想既然每个人都为DI写过文章,让我问几个问题。。
当您有一个DI配置,其中所有实际实现(而不是接口)都将被注入到一个类中(例如控制器的服务),为什么这不是某种硬编码?如果我想在运行时更改对象怎么办?例如,我的配置已经表明,当我实例化MyController时,为FileLogger注入ILogger。但我可能想注入DatabaseLogger。每次我想更改AClass需要的对象时,我都需要查看两个地方——类本身和配置文件。这是如何让生活更轻松的?如果没有注入Aproperty的AClass,是否更难模拟?回到第一个问题。如果使用new object()不好,我们为什么要注入实现而不是接口?我想你们很多人都在说我们实际上是在注入接口,但配置要求您指定接口的实现。。不在运行时。。它在编译时是硬编码的。
这是基于@Adam N发布的答案。
为什么PersonService不再需要担心GroupMembershipService?您刚才提到了GroupMembership依赖于多个东西(对象/财产)。如果PService中需要GMService,您应该将其作为属性。不管你是否注射,你都可以模仿它。我唯一希望它被注入的时候是GMService是否有更具体的子类,这在运行时之前你不会知道。然后您需要注入子类。或者如果您想将其用作单例或原型。老实说,配置文件中的所有内容都是硬编码的,包括它将在编译时为类型(接口)注入的子类。
编辑
Jose Maria Arranz对DI的美好评论
DI通过消除确定依赖方向和编写任何粘合代码的任何需要来提高内聚性。
错误。依赖关系的方向是XML形式或注释,依赖关系是以XML代码和注释的形式编写的。XML和注释是源代码。
DI通过使您的所有组件模块化(即可更换)并具有相互定义良好的接口来减少耦合。
错误。您不需要DI框架来构建基于接口的模块化代码。
关于可替换:使用非常简单的.properties存档和Class.forName,您可以定义哪些类可以更改。如果代码的任何类都可以更改,Java不适合您,请使用脚本语言。顺便说一句:不重新编译就不能更改注释。
在我看来,DI框架只有一个原因:锅炉板减少。有了一个做得好的工厂系统,您可以像首选的DI框架一样做得更好、更可控、更可预测,DI框架可以减少代码(XML和注释也是源代码)。问题是这种锅炉板减少在非常简单的情况下是真实的(每个类一个实例以及类似的情况),有时在现实世界中,选择适当的服务对象不像将类映射到单个对象那样容易。
其他回答
这是我见过的关于依赖注入和依赖注入容器的最简单的解释:
无依赖注入
应用程序需要Foo(例如控制器),因此:应用程序创建Foo应用程序调用FooFoo需要Bar(例如服务),因此:Foo创建BarFoo调用Bar酒吧需要Bim(服务、存储库,…),因此:条形图创建Bim酒吧有点事
使用依赖注入
应用程序需要Foo,需要Bar,需要Bim,因此:应用程序创建Bim应用程序创建Bar并赋予它Bim应用程序创建Foo并给它Bar应用程序调用FooFoo调用Bar酒吧有点事
使用依赖注入容器
应用程序需要Foo,因此:应用程序从容器中获取Foo,因此:容器创建Bim容器创建Bar并赋予它Bim容器创建Foo并给它Bar应用程序调用FooFoo调用Bar酒吧有点事
依赖注入和依赖注入容器是不同的:
依赖注入是一种编写更好代码的方法DI容器是帮助注入依赖项的工具
您不需要容器来执行依赖注入。然而,容器可以帮助您。
以上所有答案都很好,我的目的是用一种简单的方式解释这个概念,这样任何没有编程知识的人都可以理解这个概念
依赖注入是帮助我们以更简单的方式创建复杂系统的设计模式之一。
我们可以在日常生活中看到这种模式的广泛应用。其中一些例子是录音机、VCD、CD驱动器等。
上图是20世纪中期的便携式磁带录音机。来源
录音机的主要目的是记录或重放声音。
在设计系统时,需要一个卷轴来录制或播放声音或音乐。设计该系统有两种可能性
我们可以把卷轴放在机器里我们可以为卷轴提供一个可以放置的钩子。
如果我们使用第一个,我们需要打开机器来更换卷轴。如果我们选择第二种方式,即为卷轴设置挂钩,那么通过改变卷轴,我们可以获得播放任何音乐的额外好处。并且还将功能减少到只播放卷轴中的任何内容。
类似地,依赖注入是将依赖性外部化以仅关注组件的特定功能的过程,以便独立组件可以耦合在一起形成一个复杂的系统。
我们通过使用依赖注入获得的主要好处。
高内聚力和松耦合。外部化依赖,只关注责任。将事物作为组件,并结合起来形成具有高性能的大型系统。它有助于开发高质量的组件,因为它们是独立开发的,并且经过了适当的测试。如果一个组件出现故障,用另一个组件替换它会有帮助。
如今,这些概念构成了编程界众所周知的框架的基础。Spring Angular等是基于这一概念构建的著名软件框架
依赖注入是一种模式,用于创建其他对象依赖的对象实例,而在编译时不知道将使用哪个类来提供该功能,或者简单地将财产注入对象的方法称为依赖注入。
依赖注入示例
以前我们编写的代码是这样的
Public MyClass{
DependentClass dependentObject
/*
At somewhere in our code we need to instantiate
the object with new operator inorder to use it or perform some method.
*/
dependentObject= new DependentClass();
dependentObject.someMethod();
}
通过依赖注入,依赖注入器将为我们完成实例化
Public MyClass{
/* Dependency injector will instantiate object*/
DependentClass dependentObject
/*
At somewhere in our code we perform some method.
The process of instantiation will be handled by the dependency injector
*/
dependentObject.someMethod();
}
你也可以阅读
控制反转与依赖注入的区别
让我们想象一下,你想去钓鱼:
没有依赖注入,你需要自己处理所有的事情。你需要找一艘船,买一根鱼竿,寻找诱饵等等。当然,这是可能的,但这给你带来了很多责任。在软件方面,这意味着你必须对所有这些东西进行查找。通过依赖注入,其他人负责所有准备工作,并为您提供所需的设备。你将收到(“被注射”)船、鱼竿和鱼饵——所有这些都准备好使用。
公认的答案是一个好答案——但我想补充一点,DI非常像代码中避免硬编码常量的经典做法。
当您使用诸如数据库名称之类的常量时,您可以将其从代码内部快速移动到某个配置文件,并将包含该值的变量传递到需要它的位置。这样做的原因是,这些常量通常比代码的其他部分更频繁地更改。例如,如果您想在测试数据库中测试代码。
在面向对象编程的世界中,DI与此类似。那里的值而不是常量文字是整个对象-但是将创建它们的代码从类代码中移出的原因是相似的-对象的更改比使用它们的代码更频繁。一个重要的情况是需要进行这样的改变,那就是测试。
使依赖注入概念易于理解。让我们以开关按钮为例来切换(打开/关闭)灯泡。
无依赖注入
Switch需要事先知道我连接到哪个灯泡(硬编码依赖项)。所以
开关->永久灯泡//开关直接连接到永久灯泡,测试不容易
Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}
使用依赖注入
开关只知道我需要打开/关闭传递给我的灯泡。所以,
开关->灯泡1或灯泡2或夜灯泡(注入依赖性)
Switch(AnyBulb){ //pass it whichever bulb you like
AnyBulb.Toggle();
}
修改开关和灯泡的James示例:
public class SwitchTest {
TestToggleBulb() {
MockBulb mockbulb = new MockBulb();
// MockBulb is a subclass of Bulb, so we can
// "inject" it here:
Switch switch = new Switch(mockBulb);
switch.ToggleBulb();
mockBulb.AssertToggleWasCalled();
}
}
public class Switch {
private Bulb myBulb;
public Switch() {
myBulb = new Bulb();
}
public Switch(Bulb useThisBulbInstead) {
myBulb = useThisBulbInstead;
}
public void ToggleBulb() {
...
myBulb.Toggle();
...
}
}`