已经发布了几个关于dependency injection的具体问题的问题,例如何时使用它以及它的框架。然而,
什么是依赖注入以及何时/为什么应该或不应该使用它?
答案 0 :(得分:2208)
到目前为止,我发现的最佳定义是one by James Shore:
“依赖注入”是一个25美元 5美分概念的术语。 [...] 依赖注入意味着给予 对象的实例变量。 [...]。
an article by Martin Fowler也可能有用。
依赖注入基本上是提供对象所需的对象(它的依赖关系),而不是让它自己构造它们。这是一种非常有用的测试技术,因为它允许模拟或删除依赖项。
可以通过多种方式(例如构造函数注入或setter注入)将依赖项注入到对象中。甚至可以使用专门的依赖注入框架(例如Spring)来做到这一点,但它们当然不是必需的。您不需要这些框架具有依赖注入。显式地实例化和传递对象(依赖关系)与框架注入一样好。
答案 1 :(得分:1814)
依赖注入将依赖关系传递给其他对象或框架(依赖注入器)。
依赖注入使测试更容易。注入可以通过构造函数完成。
SomeClass()
的构造函数如下:
public SomeClass() {
myObject = Factory.getObject();
}
<强>问题强>:
如果myObject
涉及复杂的任务(如磁盘访问或网络访问),则{strong}很难对SomeClass()
进行单元测试。程序员必须模拟myObject
并且可能拦截工厂调用。
替代解决方案:
myObject
作为参数传递给构造函数public SomeClass (MyClass myObject) {
this.myObject = myObject;
}
myObject
可以直接传递,这使得测试更容易。
在没有依赖注入的情况下,在单元测试中隔离组件更加困难。
2013年,当我写这个答案时,这是Google Testing Blog的主题。它仍然是我的最大优势,因为程序员并不总是需要在运行时设计中具有额外的灵活性(例如,对于服务定位器或类似的模式)。程序员经常需要在测试期间隔离类。
答案 2 :(得分:575)
任何应用程序都由许多对象组成,这些对象相互协作以执行一些有用的东西。传统上,每个对象都负责获取它自己对与之协作的依赖对象(依赖项)的引用。这导致高度耦合的类和难以测试的代码。
例如,考虑一个Car
对象。
Car
取决于车轮,发动机,燃油,电池等运行。传统上,我们定义此类依赖对象的品牌以及Car
对象的定义。
没有依赖注入(DI):
class Car{
private Wheel wh = new NepaliRubberWheel();
private Battery bt = new ExcideBattery();
//The rest
}
此处,Car
对象负责创建依赖对象。
如果我们想要在初始Wheel
穿孔之后更改其依赖对象的类型(比如NepaliRubberWheel()
),该怎么办?
我们需要使用新的依赖关系ChineseRubberWheel()
重新创建Car对象,但只有Car
制造商才能这样做。
那么Dependency Injection
为我们做了什么......?
使用依赖项注入时,会在运行时为对象提供其依赖项,而不是编译时间(汽车制造时间)。
这样我们现在可以随时更改Wheel
。在此处,dependency
(wheel
)可以在运行时注入Car
。
使用依赖注入后:
在这里,我们在运行时注入 依赖关系(Wheel和Battery)。因此术语:依赖注入。
class Car{
private Wheel wh = // Inject an Instance of Wheel (dependency of car) at runtime
private Battery bt = // Inject an Instance of Battery (dependency of car) at runtime
Car(Wheel wh,Battery bt) {
this.wh = wh;
this.bt = bt;
}
//Or we can have setters
void setWheel(Wheel wh) {
this.wh = wh;
}
}
答案 3 :(得分:248)
依赖注入是一种实践,其中对象的设计方式是从其他代码段接收对象的实例,而不是在内部构造它们。这意味着可以替换实现对象所需的接口的任何对象而无需更改代码,这简化了测试,并改善了解耦。
例如,请考虑以下条款:
public class PersonService {
public void addManager( Person employee, Person newManager ) { ... }
public void removeManager( Person employee, Person oldManager ) { ... }
public Group getGroupByManager( Person manager ) { ... }
}
public class GroupMembershipService() {
public void addPersonToGroup( Person person, Group group ) { ... }
public void removePersonFromGroup( Person person, Group group ) { ... }
}
在此示例中,PersonService::addManager
和PersonService::removeManager
的实现需要GroupMembershipService
的实例才能完成其工作。如果没有依赖注入,传统的方法是在GroupMembershipService
的构造函数中实例化一个新的PersonService
,并在两个函数中使用该实例属性。但是,如果GroupMembershipService
的构造函数有多个需要的东西,或者更糟糕的是,需要在GroupMembershipService
上调用一些初始化“setter”,代码增长得相当快,而{ {1}}现在不仅取决于PersonService
,还取决于GroupMembershipService
依赖的所有其他内容。此外,GroupMembershipService
的链接被硬编码到GroupMembershipService
中,这意味着您不能为了测试目的而“虚拟”PersonService
,或者在GroupMembershipService
的不同部分使用策略模式。你的申请。
使用依赖注入,而不是在GroupMembershipService
中实例化PersonService
,而是将其传递给PersonService
构造函数,或者添加属性(getter和setter)设置它的本地实例。这意味着您的PersonService
不再需要担心如何创建GroupMembershipService
,它只接受它给出的那些,并与它们一起使用。这也意味着任何作为GroupMembershipService
的子类或实现GroupMembershipService
接口的东西都可以“注入”PersonService
,PersonService
不需要了解变化。
答案 4 :(得分:155)
答案 5 :(得分:114)
让我们尝试使用 Car 和引擎类的简单示例,任何汽车都需要引擎才能到达任何地方,至少目前是这样。所以下面代码看起来没有依赖注入。
public class Car
{
public Car()
{
GasEngine engine = new GasEngine();
engine.Start();
}
}
public class GasEngine
{
public void Start()
{
Console.WriteLine("I use gas as my fuel!");
}
}
为了实例化Car类,我们将使用下一个代码:
Car car = new Car();
我们与GasEngine紧密结合的代码问题,如果我们决定将其更改为ElectricityEngine,那么我们将需要重写Car类。而且应用程序越大,我们将需要添加和使用新型引擎的问题和头痛就越多。
换句话说,这种方法是我们的高级Car类依赖于较低级别的GasEngine类,它违反了SOLID的依赖性倒置原则(DIP)。 DIP建议我们应该依赖于抽象,而不是具体的课程。所以为了满足这个要求,我们引入了IEngine接口和重写代码,如下所示:
public interface IEngine
{
void Start();
}
public class GasEngine : IEngine
{
public void Start()
{
Console.WriteLine("I use gas as my fuel!");
}
}
public class ElectricityEngine : IEngine
{
public void Start()
{
Console.WriteLine("I am electrocar");
}
}
public class Car
{
private readonly IEngine _engine;
public Car(IEngine engine)
{
_engine = engine;
}
public void Run()
{
_engine.Start();
}
}
现在我们的Car类仅依赖于IEngine接口,而不是特定的引擎实现。 现在,唯一的技巧是如何创建Car的实例并为其提供一个实际的具体Engine类,如GasEngine或ElectricityEngine。这就是依赖注入的用武之地。
Car gasCar = new Car(new GasEngine());
gasCar.Run();
Car electroCar = new Car(new ElectricityEngine());
electroCar.Run();
这里我们基本上将我们的依赖项(Engine实例)注入(传递)给Car构造函数。所以现在我们的类在对象及其依赖项之间存在松耦合,我们可以轻松添加新类型的引擎而无需更改Car类。
依赖注入的主要好处是类更松散耦合,因为它们没有硬编码依赖项。这遵循上面提到的依赖性倒置原则。类不是引用特定的实现,而是请求抽象(通常是接口),这些抽象是在构造类时提供给它们的。
所以最后依赖注入只是一种技术 实现对象及其依赖项之间的松散耦合。 而不是直接实例化类所需的依赖项 为了执行其操作,将依赖关系提供给类 (最常见的)通过构造函数注入。
此外,当我们有很多依赖项时,最好使用Inversion of Control(IoC)容器,我们可以告诉哪些接口应该映射到所有依赖项的具体实现,我们可以让它为我们解决这些依赖项当它构造我们的对象时。例如,我们可以在IoC容器的映射中指定 IEngine 依赖关系应该映射到 GasEngine 类,当我们向IoC容器询问我们的实例时 Car 类,它会自动构建我们的 Car 类,并传入 GasEngine 依赖项。
更新:最近观看了Julie Lerman关于EF Core的课程,并且也喜欢她关于DI的简短定义。
依赖注入是一种允许应用程序注入的模式 对象需要它们,而不是强制它们 要负责这些对象的类。它允许您的代码 更松散耦合,实体框架核心插入到这一点 服务体系。
答案 6 :(得分:103)
让我们想象你想去钓鱼:
如果没有依赖注入,您需要自己处理所有事情。你需要找一条船,买一根钓竿,寻找诱饵等等。当然,这是可能的,但它给你带来了很多责任。在软件方面,这意味着您必须对所有这些事情进行查找。
通过依赖注入,其他人负责所有准备工作并为您提供所需的设备。您将收到(“注入”)船,钓竿和诱饵 - 所有这些都可以使用。
答案 7 :(得分:87)
This是我见过的依赖注入和依赖注入容器的最简单的解释:
依赖注入和依赖注入容器是不同的东西:
您不需要容器来执行依赖项注入。但是容器可以帮助你。
答案 8 :(得分:51)
没有&#34;#34;依赖注入&#34;只是意味着使用参数化构造函数和公共setter?
James Shore's article shows the following examples for comparison
没有依赖注入的构造函数:
public class Example {
private DatabaseThingie myDatabase;
public Example() {
myDatabase = new DatabaseThingie();
}
public void doStuff() {
...
myDatabase.getData();
...
}
}
具有依赖注入的构造函数:
public class Example {
private DatabaseThingie myDatabase;
public Example(DatabaseThingie useThisDatabaseInstead) {
myDatabase = useThisDatabaseInstead;
}
public void doStuff() {
...
myDatabase.getData();
...
}
}
答案 9 :(得分:36)
什么是依赖注入(DI)?
正如其他人所说,依赖注入(DI)消除了我们感兴趣的类(消费者类)所依赖的其他对象实例的直接创建和生命管理的责任(在UML sense)。这些实例通常作为构造函数参数或通过属性设置器传递给我们的使用者类(依赖对象实例化和传递给使用者类的管理通常由控制反转(IoC)执行容器,但这是另一个话题。)
DI,DIP和SOLID
具体而言,在Robert C Martin SOLID principles of Object Oriented Design的范例中,DI
是Dependency Inversion Principle (DIP)的可能实现之一。 DIP is the D
of the SOLID
mantra - 其他DIP实现包括服务定位器和插件模式。
DIP的目标是解除类之间紧密,具体的依赖关系,而是通过抽象来放松耦合,这可以通过interface
,abstract class
或{来实现。 {1}},取决于所使用的语言和方法。
如果没有DIP,我们的代码(我称之为“消费类&#39;”)直接耦合到具体的依赖关系,并且通常还有责任知道如何获取和管理,这种依赖的一个实例,即概念上:
pure virtual class
在应用DIP之后,要求被放宽,并且已经消除了获取和管理"I need to create/use a Foo and invoke method `GetBar()`"
依赖关系的生命周期的担忧:
Foo
为什么要使用DIP(和DI)?
以这种方式解耦类之间的依赖关系允许容易替换这些依赖类与其他实现同时满足抽象的先决条件(例如,依赖关系可以与同一接口的另一个实现切换)。此外,正如其他人所提到的,可能 通过DIP解耦类的最常见原因是允许单独测试消费类,因为这些相同的依赖关系现在可以被存根和/或模拟。
DI的一个结果是依赖对象实例的生命周期管理不再由消费类控制,因为依赖对象现在被传递到消费类中(通过构造函数或setter注入)。
可以通过不同方式查看:
"I need to invoke something which offers `GetBar()`"
获取实例,并在完成后处理这些实例。何时使用DI?
Create
是线程安全的 - 如果我们将它作为单例并将相同的实例注入所有消费者会怎么样?)示例强>
这是一个简单的C#实现。鉴于以下消费类:
MyDepClass
虽然看似无害,但它对另外两个类public class MyLogger
{
public void LogRecord(string somethingToLog)
{
Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);
}
}
和static
有两个System.DateTime
依赖关系,这不仅限制了日志输出选项(如果没有,则记录到控制台将毫无价值一个人在看,但更糟糕的是,鉴于对非确定性系统时钟的依赖,很难自动测试。
然而,我们可以将System.Console
应用于此类,将时间戳的关注抽象为依赖关系,并将DIP
仅耦合到一个简单的接口:
MyLogger
我们还可以放宽对public interface IClock
{
DateTime Now { get; }
}
的依赖关系到抽象,例如Console
。依赖注入通常实现为TextWriter
注入(将依赖项的抽象作为参数传递给消费类的构造函数)或constructor
(通过Setter Injection
setter传递依赖项或定义了setXyz()
的.Net属性)。构造函数注入是首选,因为这可以保证类在构造后处于正确的状态,并允许内部依赖项字段标记为{set;}
(C#)或readonly
(Java)。所以在上面的例子中使用构造函数注入,这给我们留下了:
final
(需要提供具体的public class MyLogger : ILogger // Others will depend on our logger.
{
private readonly TextWriter _output;
private readonly IClock _clock;
// Dependencies are injected through the constructor
public MyLogger(TextWriter stream, IClock clock)
{
_output = stream;
_clock = clock;
}
public void LogRecord(string somethingToLog)
{
// We can now use our dependencies through the abstraction
// and without knowledge of the lifespans of the dependencies
_output.Write("{0:yyyy-MM-dd HH:mm:ss} - {1}", _clock.Now, somethingToLog);
}
}
,当然可以恢复为Clock
,并且需要通过构造函数注入由IoC容器提供这两个依赖项。
可以构建自动化单元测试,这可以明确证明我们的记录器工作正常,因为我们现在可以控制依赖关系 - 时间,我们可以监视书面输出:
DateTime.Now
后续步骤
依赖注入总是与Inversion of Control container(IoC)相关联,注入(提供)具体的依赖实例,以及管理生命周期实例。在配置/引导过程中,[Test]
public void LoggingMustRecordAllInformationAndStampTheTime()
{
// Arrange
var mockClock = new Mock<IClock>();
mockClock.Setup(c => c.Now).Returns(new DateTime(2015, 4, 11, 12, 31, 45));
var fakeConsole = new StringWriter();
// Act
new MyLogger(fakeConsole, mockClock.Object)
.LogRecord("Foo");
// Assert
Assert.AreEqual("2015-04-11 12:31:45 - Foo", fakeConsole.ToString());
}
容器允许定义以下内容:
IoC
,返回IBar
实例&#34; )< / LI>
可以为每个依赖关系的生命周期管理设置ConcreteBar
之类的协议,并将根据配置的生命周期管理承担IDisposable
个依赖关系的责任。通常,一旦配置/引导IoC容器,它们就会在后台无缝运行,允许编码器专注于手头的代码,而不是担心依赖。
DI友好代码的关键是避免类的静态耦合,而不是使用new()来创建依赖项
如上例所示,依赖关系的解耦确实需要一些设计工作,对于开发人员来说,需要一种范式转换来直接打破Disposing
依赖关系的习惯,而是信任容器来管理依赖关系。
但是好处很多,特别是在彻底测试你感兴趣的课程的能力方面。
注意:POCO / POJO /序列化DTO /实体图/匿名JSON投影等的创建/映射/投影(通过new
) - 即&#34;仅数据& #34;从方法中使用或返回的类或记录不被视为依赖性(在UML意义上)并且不受DI的约束。使用new ..()
投影这些就好了。
答案 10 :(得分:35)
使依赖注入概念易于理解。我们举一个开关按钮的例子来切换(打开/关闭)灯泡。
开关需要事先知道我连接的灯泡(硬编码依赖)。所以,
开关 - &gt; PermanentBulb //开关直接连接到永久灯泡,无法轻松进行测试
Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}
Switch只知道我需要打开/关闭哪个Bulb传递给我。所以,
开关 - &gt; Bulb1 OR Bulb2 OR NightBulb(注入依赖)
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();
...
}
}`
答案 11 :(得分:25)
依赖注入(DI)的重点是保持应用程序源代码清洁和稳定:
实际上,每种设计模式都会将问题分开,以使将来的更改影响最小文件。
DI的特定域是依赖配置和初始化的委派。
如果您偶尔在Java之外工作,请回想一下source
在许多脚本语言中经常使用的方式(Shell,Tcl等,甚至Python中的import
误用于此目的)。
考虑简单的dependent.sh
脚本:
#!/bin/sh
# Dependent
touch "one.txt" "two.txt"
archive_files "one.txt" "two.txt"
脚本依赖:它不能自己成功执行(archive_files
未定义)。
您在archive_files
实施脚本中定义archive_files_zip.sh
(在这种情况下使用zip
):
#!/bin/sh
# Dependency
function archive_files {
zip files.zip "$@"
}
您可以使用source
&#34;容器&#34;而不是injector.sh
- 直接在从属脚本中执行脚本。它包装了&#34;组件&#34;:
#!/bin/sh
# Injector
source ./archive_files_zip.sh
source ./dependent.sh
archive_files
依赖刚刚注入到依赖脚本中。
您可以使用archive_files
或tar
注入实现xz
的依赖项。
如果dependent.sh
脚本直接使用依赖项,则该方法将被称为依赖项查找(与依赖项注入相反):
#!/bin/sh
# Dependent
# dependency look-up
source ./archive_files_zip.sh
touch "one.txt" "two.txt"
archive_files "one.txt" "two.txt"
现在的问题是依赖&#34;组件&#34;必须自己进行初始化。
&#34;组件&#34;的源代码既不是干净也不是稳定,因为依赖项初始化的每个更改都需要新版本的& #34;组件&#34;的源代码文件。
DI并不像Java框架那样强调和推广。
但它是解决以下问题的通用方法:
仅使用依赖项查找配置无效,因为每个依赖项(例如,新的身份验证类型)以及受支持的依赖项类型(例如,新数据库类型)的配置参数数量可能会发生变化。
答案 12 :(得分:18)
以上所有答案都很好,我的目的是以简单的方式解释这个概念,这样没有编程知识的人也可以理解概念
依赖注入是帮助我们以更简单的方式创建复杂系统的设计模式之一。
我们可以在日常生活中看到这种模式的广泛应用。 一些例子是录音机,VCD,CD驱动器等。
上图是20世纪中期卷轴式便携式录音机的图像。 Source
录音机的主要目的是录制或播放声音。
在设计系统时,需要使用卷轴来录制或播放声音或音乐。设计该系统有两种可能性
如果我们使用第一个,我们需要打开机器来更换卷轴。 如果我们选择第二个,即放置一个卷轴,我们通过改变卷轴播放任何音乐都有额外的好处。并且还将功能仅限于播放卷轴中的任何内容。
同样明智的依赖注入是将依赖项外部化以仅关注组件的特定功能的过程,以便独立组件可以耦合在一起形成复杂系统。
使用依赖注入实现的主要好处。
现在有一天,这些概念构成了编程世界中众所周知的框架的基础。 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();
}
你也可以阅读
Difference between Inversion of Control & Dependency Injection
答案 13 :(得分:17)
依赖注入(DI)意味着解耦彼此依赖的对象。假设对象A依赖于对象B,因此想法是将这些对象彼此分离。我们不需要使用new关键字对对象进行硬编码,而是在编译时尽管在运行时共享对象的依赖关系。 如果我们谈论
我们不需要使用new关键字对对象进行硬编码,而是在配置文件中定义bean依赖关系。弹簧容器将负责连接所有。
IOC是一个通用概念,它可以用许多不同的方式表达,而依赖注入是IOC的一个具体例子。
当容器调用具有多个参数的类构造函数时,就完成了基于构造函数的DI,每个参数都表示对其他类的依赖。
public class Triangle {
private String type;
public String getType(){
return type;
}
public Triangle(String type){ //constructor injection
this.type=type;
}
}
<bean id=triangle" class ="com.test.dependencyInjection.Triangle">
<constructor-arg value="20"/>
</bean>
基于Setter的DI是在调用无参数构造函数或无参数静态工厂方法来实例化bean之后,通过容器调用bean上的setter方法来完成的。
public class Triangle{
private String type;
public String getType(){
return type;
}
public void setType(String type){ //setter injection
this.type = type;
}
}
<!-- setter injection -->
<bean id="triangle" class="com.test.dependencyInjection.Triangle">
<property name="type" value="equivialteral"/>
注意: 使用构造函数参数作为必需依赖项和使用可选依赖项的setter是一个很好的经验法则。请注意,如果我们在setter上使用基于@Required注释的注释,则可以使用setter作为必需的依赖项。
答案 14 :(得分:15)
我能想到的最好的比喻是手术室里的外科医生和他的助手,外科医生是主要人员,他的助手在需要时提供各种手术部件,以便外科医生可以集中精力关于他最擅长的一件事(手术)。没有助手,外科医生每次需要时都必须自己拿到组件。
简称DI,是一种通过向组件提供依赖组件来消除组件的常见附加责任(负担)的技术。
DI让您更接近单一责任(SR)原则,例如surgeon who can concentrate on surgery
。
何时使用DI:我建议在几乎所有生产项目(小型/大型)中使用DI,特别是在不断变化的商业环境中:)
原因:因为您希望您的代码易于测试,可模拟等,以便您可以快速测试您的更改并将其推向市场。除此之外,为什么你不会在你有更多控制权的代码库之旅中有很多很棒的免费工具/框架来支持你。
答案 15 :(得分:15)
在转到技术说明之前,先通过一个真实的示例对其进行可视化,因为您会发现很多技术知识可以学习依赖注入,但是大多数人无法理解它的核心概念。
在第一张图片中,假设您有一个汽车工厂,其中有很多单位。汽车实际上是在装配单元中建造的,但它需要发动机,座椅和车轮。因此,装配单元依赖于所有这些单元,它们是工厂的依赖项。
您可能会觉得现在很难在这个工厂中维护所有任务,因为除了主要任务(在组装部门组装汽车)外,您还必须专注于其他部门。现在,它的维护成本非常高,工厂厂房巨大,因此需要多花一些钱来租房。
现在,看第二张图片。如果您发现某些提供商公司会以比您自己生产的价格便宜的价格为您提供方向盘,座椅和引擎,那么您现在就可以不需要在您的工厂制造它们。您现在可以租用装配单元的一栋较小的建筑物,这将减少维护工作并减少额外的租赁费用。现在,您也可以只专注于主要任务(汽车装配)。
现在我们可以说,组装汽车的所有依赖项都是从供应商出厂时注入的。这是真实的 依赖注入(DI) 的示例。
现在用专业术语来说,依赖注入是一种技术,通过这种技术,一个对象(或静态方法)可以提供另一个对象的依赖关系。因此,将创建对象的任务转移给其他人并直接使用依赖项的过程称为依赖项注入。
答案 16 :(得分:13)
这意味着对象应该只具有执行其工作所需的依赖项,并且依赖项应该很少。此外,对象的依赖关系应该在接口上,而不是在“具体”对象上,如果可能的话。 (具体对象是使用关键字new创建的任何对象。)松散耦合可提高可重用性,更易于维护,并允许您轻松提供“模拟”对象来代替昂贵的服务。
“依赖注入”(DI)也称为“控制反转”(IoC),可用作鼓励这种松散耦合的技术。
实施DI有两种主要方法:
这是将对象依赖项传递给其构造函数的技术。
请注意,构造函数接受接口而不是具体对象。另请注意,如果orderDao参数为null,则抛出异常。这强调了接收有效依赖的重要性。在我看来,构造函数注入是赋予对象依赖性的首选机制。开发人员在调用对象时很清楚需要将哪些依赖项提供给“Person”对象才能正确执行。
但请考虑以下示例...假设您有一个具有十个没有依赖关系的方法的类,但您添加的新方法确实依赖于IDAO。您可以更改构造函数以使用构造函数注入,但这可能会强制您更改所有构造函数调用。或者,您可以添加一个新的构造函数来获取依赖项,但是开发人员如何轻松地知道何时使用一个构造函数而不是另一个构造函数。最后,如果创建依赖项非常昂贵,为什么它应该只在很少使用的情况下创建并传递给构造函数? “Setter Injection”是另一种DI技术,可用于此类情况。
Setter Injection不会强制将依赖项传递给构造函数。相反,依赖项设置为有需要的对象公开的公共属性。如前所述,这样做的主要动机包括:
以下是上述代码的示例:
public class Person {
public Person() {}
public IDAO Address {
set { addressdao = value; }
get {
if (addressdao == null)
throw new MemberAccessException("addressdao" +
" has not been initialized");
return addressdao;
}
}
public Address GetAddress() {
// ... code that uses the addressdao object
// to fetch address details from the datasource ...
}
// Should not be called directly;
// use the public property instead
private IDAO addressdao;
答案 17 :(得分:13)
示例,我们有2个课程Client
和Service
。 Client
将使用Service
public class Service {
public void doSomeThingInService() {
// ...
}
}
方式1)
public class Client {
public void doSomeThingInClient() {
Service service = new Service();
service.doSomeThingInService();
}
}
方式2)
public class Client {
Service service = new Service();
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
方式3)
public class Client {
Service service;
public Client() {
service = new Service();
}
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
1)2)3)使用
Client client = new Client();
client.doSomeThingInService();
<强>优点强>
<强>缺点强>
Client
类Service
构造函数时,我们需要在所有位置更改代码create Service
object 方式1)构造函数注入
public class Client {
Service service;
Client(Service service) {
this.service = service;
}
// Example Client has 2 dependency
// Client(Service service, IDatabas database) {
// this.service = service;
// this.database = database;
// }
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
使用
Client client = new Client(new Service());
// Client client = new Client(new Service(), new SqliteDatabase());
client.doSomeThingInClient();
方式2) Setter注入
public class Client {
Service service;
public void setService(Service service) {
this.service = service;
}
public void doSomeThingInClient() {
service.doSomeThingInService();
}
}
使用
Client client = new Client();
client.setService(new Service());
client.doSomeThingInClient();
方式3)界面注入
检查https://en.wikipedia.org/wiki/Dependency_injection
===
现在,此代码已经跟随Dependency Injection
,并且测试Client
类更容易
但是,我们仍然会多次使用new Service()
,并且在更改Service
构造函数时效果不佳。为了防止它,我们可以像使用DI注入器一样
1)简单的手册Injector
public class Injector {
public static Service provideService(){
return new Service();
}
public static IDatabase provideDatatBase(){
return new SqliteDatabase();
}
public static ObjectA provideObjectA(){
return new ObjectA(provideService(...));
}
}
是强>
Service service = Injector.provideService();
2)使用库:适用于Android dagger2
<强>优点强>
Service
时,只需在Injector class Constructor Injection
,当您查看Client
的构造函数时,您将看到Client
类缺点
Constructor Injection
,则会在创建Service
时创建Client
对象,有时我们会使用Client
类中的函数而不使用Service
创建Service
被浪费了https://en.wikipedia.org/wiki/Dependency_injection
依赖项是可以使用的对象(
Service
)
注入是将依赖项(Service
)传递给将使用它的依赖对象(Client
)
答案 18 :(得分:10)
我认为既然每个人都为DI写过,我就问几个问题。
这是基于@Adam N发布的答案。
为什么PersonService不再需要担心GroupMembershipService?您刚才提到GroupMembership有多个依赖的东西(对象/属性)。如果PService中需要GMService,您可以将其作为属性。无论你是否注射它,你都可以嘲笑它。我唯一希望它被注入的是GMService是否有更具体的子类,直到运行时才能知道。然后你想要注入子类。或者,如果您想将其用作单身或原型。说实话,配置文件的所有内容都是硬编码的,只要它在编译时要注入的类型(接口)的子类。
编辑
A nice comment by Jose Maria Arranz on DI
DI通过消除确定依赖方向的任何需要并编写任何胶水代码来增加凝聚力。
假。依赖关系的方向是XML形式或注释,您的依赖关系被编写为XML代码和注释。 XML和注释是源代码。
DI通过使所有组件模块化(即可替换)并且彼此具有明确定义的接口来减少耦合。
假。您不需要DI框架来构建基于接口的模块化代码。
关于可替换:使用非常简单的.properties存档和Class.forName,您可以定义可以更改的类。如果可以更改任何类代码,Java不适合您,请使用脚本语言。顺便说一下:如果不重新编译就无法更改注释。
在我看来,DI框架只有一个原因:锅炉板减少。使用完善的工厂系统,您可以做同样的,更可控的,更可预测的DI框架,DI框架承诺减少代码(XML和注释也是源代码)。问题是这种锅炉板减少在非常非常简单的情况下是真实的(一个实例 - 每个类和类似),有时在现实世界中挑选适当的服务对象并不像将类映射到单个对象那么容易。
答案 19 :(得分:8)
Dependency Injection表示一种代码(例如一个类)可以访问依赖项(代码的其他部分,例如其他类,它)的方式(实际上任意方式)依赖于)模块化的方式,没有硬编码(因此他们可以自由地改变或被覆盖,甚至可以根据需要在其他时间加载)
(和ps,是的,它变成了一个过于夸张的25美元名称,一个相当简单的概念),我的.25
美分
答案 20 :(得分:7)
我知道已有很多答案,但我发现这非常有用:http://tutorials.jenkov.com/dependency-injection/index.html
无依赖性:
public class MyDao {
protected DataSource dataSource =
new DataSourceImpl("driver", "url", "user", "password");
//data access methods...
public Person readPerson(int primaryKey) {...}
}
相关性:
public class MyDao {
protected DataSource dataSource = null;
public MyDao(String driver, String url, String user, String
password){
this.dataSource = new DataSourceImpl(driver, url, user, password);
}
//data access methods...
public Person readPerson(int primaryKey)
{...}
}
注意DataSourceImpl
实例化如何移动到构造函数中。构造函数采用四个参数,即DataSourceImpl
所需的四个值。虽然MyDao
类仍然依赖于这四个值,但它本身不再满足这些依赖性。它们由创建MyDao
实例的任何类提供。
答案 21 :(得分:7)
流行的答案是无益的,因为它们以一种无用的方式定义依赖注入。让我们同意依赖&#34;依赖&#34;我们指的是我们的对象X需要的一些预先存在的其他对象。但我们并不是说我们正在做依赖注射&#34;当我们说
$foo = Foo->new($bar);
我们只是将调用传递给构造函数。自建造者发明以来,我们一直在做这件事。
&#34;依赖注入&#34;被认为是一种&#34;反转控制&#34;,这意味着某些逻辑被从调用者中取出。当调用者传入参数时,情况并非如此,因此如果是DI,则DI不会意味着控制反转。
DI表示调用者和构造函数之间存在一个中间级别,用于管理依赖关系。 Makefile是依赖注入的一个简单示例。 &#34;来电者&#34;是打字的人#34; make bar&#34;在命令行上,&#34;构造函数&#34;是编译器。 Makefile指定bar依赖于foo,它执行
gcc -c foo.cpp; gcc -c bar.cpp
之前
gcc foo.o bar.o -o bar
打字的人&#34;制作酒吧&#34;不需要知道酒吧取决于foo。依赖关系被注入&#34; make bar&#34;和gcc。
中间级别的主要目的不仅仅是将依赖项传递给构造函数,而是列出一个地方中的所有依赖项,并将它们隐藏在编码器中(而不是让编码器提供它们。)
通常,中间级别为构造的对象提供工厂,这些工具必须提供每个请求的对象类型必须满足的角色。这是因为通过具有隐藏施工细节的中间水平,您已经承担了工厂施加的抽象罚款,因此您不妨使用工厂。
答案 22 :(得分:6)
依赖注入是通常被称为“依赖性混淆”要求的一种可能的解决方案。依赖性混淆是一种将“明显”性质从提供依赖性到需要它的类的过程中带走的方法,并因此以某种方式模糊化对所述类的所述依赖性的提供。这不一定是坏事。事实上,通过模糊向类提供依赖的方式,类之外的东西负责创建依赖,这意味着,在各种场景中,可以向类提供不同的依赖实现,而不进行任何更改上课。这非常适合在生产和测试模式之间切换(例如,使用'模拟'服务依赖)。
不幸的是,有些人认为你需要一个专门的框架来进行依赖性混淆,并且如果你选择不使用特定的框架来做某个人,你就会成为一个“较小”的程序员。许多人认为另一个非常令人不安的神话是依赖注入是实现依赖性混淆的唯一方法。这显然是历史性的,显然是100%错误的,但是你会难以说服某些人为依赖性混淆要求提供依赖注入的替代方案。
程序员已经理解了多年来的依赖性混淆要求,并且在构思依赖注入之前和之后已经发展了许多替代解决方案。有工厂模式,但是使用ThreadLocal也有很多选项,不需要注入特定实例 - 依赖项有效地注入到线程中,这有利于使对象可用(通过方便的静态getter方法)到需要它的任何类,而不必向需要它的类添加注释,并设置复杂的XML“粘合”以实现它。当持久性需要依赖项(JPA / JDO或其他)时,它允许您更容易实现“转换持久性”,并且域模型和业务模型类完全由POJO组成(即没有特定于框架/锁定注释)。 / p>
答案 23 :(得分:5)
从书中,&#39; Well-Grounded Java Developer: Vital techniques of Java 7 and polyglot programming
DI是IoC的一种特殊形式,其中查找依赖关系的过程是 在您当前正在执行的代码的直接控制之外。
答案 24 :(得分:4)
来自Book Apress.Spring.Persistence.with.Hibernate.Oct.2010
依赖注入的目的是分离工作 从您的应用程序业务中解析外部软件组件 logic.Without依赖注入,组件的细节 访问所需的服务可能会与组件混淆 码。这增加了代码,这不仅增加了错误的可能性 臃肿,放大维护的复杂性;它耦合组件 更密切地结合在一起,使得很难修改依赖关系 重构或测试。
答案 25 :(得分:4)
依赖注入(DI)是Design Patterns中的一个,它使用OOP的基本特性 - 一个对象与另一个对象的关系。虽然继承继承了一个对象以执行更复杂和特定的另一个对象,但是关系或关联只是使用属性从一个对象创建指向另一个对象的指针。 DI的强大功能与OOP的其他功能以及接口和隐藏代码相结合。 假设我们在库中有一个客户(订户),为了简单起见,它只能借一本书。
书的界面:
package com.deepam.hidden;
public interface BookInterface {
public BookInterface setHeight(int height);
public BookInterface setPages(int pages);
public int getHeight();
public int getPages();
public String toString();
}
接下来我们可以有很多种书;其中一种是虚构的:
package com.deepam.hidden;
public class FictionBook implements BookInterface {
int height = 0; // height in cm
int pages = 0; // number of pages
/** constructor */
public FictionBook() {
// TODO Auto-generated constructor stub
}
@Override
public FictionBook setHeight(int height) {
this.height = height;
return this;
}
@Override
public FictionBook setPages(int pages) {
this.pages = pages;
return this;
}
@Override
public int getHeight() {
// TODO Auto-generated method stub
return height;
}
@Override
public int getPages() {
// TODO Auto-generated method stub
return pages;
}
@Override
public String toString(){
return ("height: " + height + ", " + "pages: " + pages);
}
}
现在订阅者可以与该书有关联:
package com.deepam.hidden;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Subscriber {
BookInterface book;
/** constructor*/
public Subscriber() {
// TODO Auto-generated constructor stub
}
// injection I
public void setBook(BookInterface book) {
this.book = book;
}
// injection II
public BookInterface setBook(String bookName) {
try {
Class<?> cl = Class.forName(bookName);
Constructor<?> constructor = cl.getConstructor(); // use it for parameters in constructor
BookInterface book = (BookInterface) constructor.newInstance();
//book = (BookInterface) Class.forName(bookName).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return book;
}
public BookInterface getBook() {
return book;
}
public static void main(String[] args) {
}
}
所有这三个类都可以隐藏它自己的实现。现在我们可以将此代码用于DI:
package com.deepam.implement;
import com.deepam.hidden.Subscriber;
import com.deepam.hidden.FictionBook;
public class CallHiddenImplBook {
public CallHiddenImplBook() {
// TODO Auto-generated constructor stub
}
public void doIt() {
Subscriber ab = new Subscriber();
// injection I
FictionBook bookI = new FictionBook();
bookI.setHeight(30); // cm
bookI.setPages(250);
ab.setBook(bookI); // inject
System.out.println("injection I " + ab.getBook().toString());
// injection II
FictionBook bookII = ((FictionBook) ab.setBook("com.deepam.hidden.FictionBook")).setHeight(5).setPages(108); // inject and set
System.out.println("injection II " + ab.getBook().toString());
}
public static void main(String[] args) {
CallHiddenImplBook kh = new CallHiddenImplBook();
kh.doIt();
}
}
如何使用依赖注入有许多不同的方法。可以将它与Singleton等组合在一起,但仍然在基本它只是通过在另一个对象内创建对象类型的属性来实现的关联。 有用性只是在功能上,我们应该反复编写的代码始终为我们前进做好准备。这就是DI与控制反转(IoC)紧密结合的原因,这意味着我们的程序通过控制另一个运行模块,它会向我们的代码注入bean。 (可以注入的每个对象都可以被签名或视为Bean。)例如,在Spring中,它通过创建和初始化 ApplicationContext 容器来完成,这对我们来说是有效的。我们只需在我们的代码中创建Context并调用bean的初始化。在那一刻,注射已经自动完成。
答案 26 :(得分:3)
依赖注入是与Spring Framework相关的概念的核心。虽然创建任何项目的框架弹簧可能会发挥至关重要的作用,这里依赖注入来自投手。
实际上,假设在java中你创建了两个不同的类,如A类和B类,以及B类中你想要在A类中使用的任何函数,那么在那时可以使用依赖注入。 在其他地方你可以创建一个类的对象,就像你可以在另一个类中注入一个完整的类以使其可访问一样。 通过这种方式可以克服依赖。
依赖性注射只是简单地使两个类别同时保持不变。
答案 27 :(得分:3)
我将对“依赖注入”提出一个稍微不同,简短而精确的定义,重点放在主要目标上,而不是技术手段上(紧随here之后):
依赖注入是创建静态,无状态的过程 服务对象图,其中每个服务都由其服务参数化 依赖性。
我们在应用程序中创建的对象(无论使用Java,C#还是其他面向对象的语言)通常分为两类之一:无状态,静态和全局“服务对象”(模块)以及有状态的,动态和本地“数据对象”。
模块图-服务对象图-通常在应用程序启动时创建。可以使用容器(例如Spring)来完成此操作,也可以通过将参数传递给对象构造函数来手动完成。两种方法都有其优缺点,但是绝对不需要在应用程序中使用DI的框架。
一个要求是,必须根据服务的依赖关系对服务进行参数化。这究竟意味着什么,取决于给定系统中使用的语言和方法。通常,这采用构造函数参数的形式,但也可以使用setters。这也意味着服务的依赖项(在调用服务方法时)对于服务的用户是隐藏的。
何时使用?我要说的是,只要应用程序足够大以至于将逻辑封装到单独的模块中,并且模块之间具有依赖关系图,那么代码的可读性和可探索性就会得到提高。
答案 28 :(得分:3)
简单来说,依赖注入(DI)是消除不同对象之间的依赖关系或紧密耦合的方法。依赖注入为每个对象提供了一致的行为。
DI是Spring的IOC负责人的实施,其中写着“不要打电话给我们,我们会打电话给你”。使用依赖注入程序员不需要使用new关键字创建对象。
对象一旦加载到Spring容器中,然后我们通过使用getBean(String beanName)方法从Spring容器中获取这些对象,只要我们需要它们就重用它们。
答案 29 :(得分:3)
依赖注入(DI)是依赖性倒置原则(DIP)实践的一部分,也称为控制反转(IoC)。基本上你需要做DIP,因为你想让你的代码更加模块化和单元测试,而不是只有一个单片系统。因此,您开始识别可以与类分离并抽象掉的代码部分。现在,需要从类外部注入抽象的实现。通常这可以通过构造函数完成。因此,您创建一个接受抽象作为参数的构造函数,这称为依赖注入(通过构造函数)。有关DIP,DI和IoC容器的更多说明,请阅读Here
答案 30 :(得分:1)
依赖注入是一种基于框架构建的“控制反转”原则的实现。
GoF的“设计模式”中所述的框架是实现主控制流逻辑的类,提高了开发人员的作用,这样Frameworks就实现了控制原理的反转。
一种实现技术而不是类层次结构的方法,这个IoC原则只是依赖注入。
DI 主要包括将类实例的映射和对该实例的类型引用委托给外部“实体”:对象,静态类,组件,框架等......
类实例是“ 依赖项 ”,调用组件与类实例的外部绑定通过引用它是“ 注入 强>”。
显然,您可以从OOP的角度以多种方式实现此技术,例如参见构造函数注入, setter注入,接口注入< / em>的
委托第三方执行将ref匹配到对象的任务,当您想要将需要某些服务的组件与同一服务实现完全分离时,这非常有用。
通过这种方式,在设计组件时,您可以专注于其架构及其特定逻辑,信任与其他对象协作的接口,而无需担心所使用的对象/服务的任何类型的实现更改,如果相同的对象也是如此你正在使用将被完全取代(显然尊重界面)。
答案 31 :(得分:1)
从Pablo Deeleman的书Christopher Noring的“学习角度-第二版”中可以找到:
“随着我们的应用程序的成长和发展,我们的每个代码实体都将在内部需要其他对象的实例,这些实例在软件工程界被称为依赖项。将此类依赖项传递给依赖客户端的动作是已知的作为注入,它还需要另一个称为注入器的代码实体的参与,注入器将负责实例化和引导所需的依赖项,以便从成功注入客户端的那一刻起就可以使用它们。这非常重要,因为客户端对如何实例化自己的依赖项一无所知,并且仅知道它们实现的接口以使用它们。”
答案 32 :(得分:1)
依赖注入是一种使分离的组件与它们的某些依赖不可知的实践,这遵循SOLID准则,
依赖倒置原则:一个人应该“依赖抽象, 不是混凝土。
“依赖关系注入”的更好实现是“合成根”设计模式,因为它允许您的组件与依赖关系注入容器分离。
我建议这篇关于成分根的出色文章 http://blog.ploeh.dk/2011/07/28/CompositionRoot/ 马克·西曼(Mark Seemann)撰写
这是本文的重点:
合成根是应用程序中的(最好是)唯一位置 模块组成的地方。
...
仅应用程序应具有成分根。图书馆和 框架不应该。
...
DI容器仅应从成分根引用。 所有其他模块都不应引用该容器。
Di-Ninja(一个依赖项注入框架)的文档是一个很好的示例,可以演示组成根和依赖项注入原理的工作方式。 https://github.com/di-ninja/di-ninja 据我所知,它是javascript中唯一实现Composition-Root设计模式的DiC。
答案 33 :(得分:1)
5岁儿童的依赖注射。
当您自己从冰箱中取出东西时,可能会引起问题。您可能会打开门,可能会得到妈妈或爸爸不想要的东西。您甚至可能正在寻找我们什至没有或已经过期的东西。
您应该做的是陈述一个需求,“我需要在午餐时喝点东西”,然后我们将确保您坐下吃饭时有一些东西。
答案 34 :(得分:1)
我们可以通过依赖注入来了解它:
class Injector {
constructor() {
this.dependencies = {};
this.register = (key, value) => {
this.dependencies[key] = value;
};
}
resolve(...args) {
let func = null;
let deps = null;
let scope = null;
const self = this;
if (typeof args[0] === 'string') {
func = args[1];
deps = args[0].replace(/ /g, '').split(',');
scope = args[2] || {};
} else {
func = args[0];
deps = func.toString().match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1].replace(/ /g, '').split(',');
scope = args[1] || {};
}
return (...args) => {
func.apply(scope || {}, deps.map(dep => self.dependencies[dep] && dep != '' ? self.dependencies[dep] : args.shift()));
}
}
}
injector = new Injector();
injector.register('module1', () => { console.log('hello') });
injector.register('module2', () => { console.log('world') });
var doSomething1 = injector.resolve(function (module1, module2, other) {
module1();
module2();
console.log(other);
});
doSomething1("Other");
console.log('--------')
var doSomething2 = injector.resolve('module1,module2,', function (a, b, c) {
a();
b();
console.log(c);
});
doSomething2("Other");
以上是javascript的实现
答案 35 :(得分:0)
任何重要的应用程序都由两个或多个类组成,这些类相互协作以执行某些业务逻辑。传统上,每个对象负责获取它自己对与其协作的对象(其依赖项)的引用。 在应用DI时,对象在创建时由一些外部实体给予它们的依赖关系,这些外部实体协调系统中的每个对象。换句话说,依赖关系被注入到对象中。
有关详细信息,请参阅enter link description here
答案 36 :(得分:0)
简单示例:
如果您需要医生,您只需去寻找(现有的)医生。您不会考虑从头开始创建医生来为您提供帮助。他已经存在,可能会为您或其他对象服务。无论您(单个对象)是否需要他,他都有权存在,因为他的目的是服务一个或多个对象。谁决定了他的存在是全能的上帝,而不是自然选择。因此,DI的优点之一是避免在宇宙的生命周期(即应用程序)中创建无用的,没有目的的多余对象。
答案 37 :(得分:-1)
简而言之,当我们在春季说依赖性时,它表示我们建立了对象,而那些对象依赖于其他一些对象
例如:您要制造一台笔记本电脑
您需要什么?硬盘,RAM,屏幕等 如果您要制造的笔记本电脑是“ X”品牌的笔记本电脑,那么您必须借用不同品牌的不同部件(考虑到所有您无法制造的东西)。如果您要构建一个依赖于其他对象的对象,则不能构建所有具有不同类的类。
class Laptop {
HitachiHD obj1=new HitachiHD();
}
这是一个松散的耦合:一个对象并不完全依赖于另一个对象。因此,这里引入了抽象的概念—如果要替换硬盘驱动器,我们将创建外部服务,该服务将注入依赖项容器对象并将其注入。
以前,我们有spring xml,如果有人想要对象,我们可以在xml文件中提供它。
在springboot中,我们不需要像Spring xml文件那样手动更改对象名称。在这里,我们使用Component和Autowired注释使它更松散地耦合。
@Component
class HitachiHD implements HardDrive {
}
在Laptop
类中,我们将在此使用它,将其标记为Autowired
@ Autowired
HardDrive obj1;
由于它是松散耦合的,因此也用于测试,因此您可以轻松地以更好的方式进行模拟和测试。