什么是依赖注入?

已经有几个问题发布了关于依赖注入的具体问题,例如何时使用它以及有哪些框架。然而,

什么是依赖注入以及何时/为什么应该或不应该使用它?

1100827 次浏览

依赖注入将依赖传递给其他对象框架(依赖注入器)。

依赖注入使测试更容易。注入可以通过构造函数完成。

SomeClass()的构造函数如下:

public SomeClass() {myObject = Factory.getObject();}

问题:如果myObject涉及复杂的任务,如磁盘访问或网络访问,则SomeClass()进行单元测试。程序员必须模拟myObject并可能拦截工厂调用。

替代解决方案

  • myObject作为参数传递给构造函数
public SomeClass (MyClass myObject) {this.myObject = myObject;}

myObject可以直接传递,这使得测试更容易。

  • 一种常见的替代方法是定义无所事事构造函数。依赖注入可以通过setter完成。(h/t@MikeVella)。
  • martinfowler记录了第三种选择(h/t@MarcDix),其中类显式实现接口为程序员希望注入的依赖项。

在没有依赖注入的情况下,很难在单元测试中隔离组件。

在2013年,当我写这个答案时,这是谷歌测试博客的一个主要主题。它仍然是我最大的优势,因为程序员并不总是需要在他们的运行时设计中额外的灵活性(例如,服务定位器或类似模式)。程序员经常需要在测试期间隔离类。

依赖注入是一种实践,其中对象的设计方式是从其他代码段接收对象的实例,而不是在内部构建它们。这意味着任何实现对象所需接口的对象都可以在不更改代码的情况下替换进来,这简化了测试并改善了解耦。

例如,考虑这些类:

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::addManagerPersonService::removeManager的实现需要GroupMembershipService的一个实例才能完成它的工作。如果没有依赖注入,传统的方法是在PersonService的构造函数中实例化一个新的GroupMembershipService,并在两个函数中使用该实例属性。然而,如果GroupMembershipService的构造函数有多个它需要的东西,或者更糟糕的是,有一些初始化“setter”需要在GroupMembershipService上调用,代码增长相当快,PersonService现在不仅依赖于GroupMembershipService,还依赖于GroupMembershipService依赖的所有其他东西。此外,与GroupMembershipService的链接被硬编码到PersonService中,这意味着您不能出于测试目的“虚拟”GroupMembershipService,也不能在应用程序的不同部分使用策略模式。

使用依赖注入,而不是在PersonService中实例化GroupMembershipService,你可以将其传递给PersonService构造函数,或者添加一个Property(getter和setter)来设置它的本地实例。这意味着你的PersonService不再需要担心如何创建GroupMembershipService,它只是接受它给定的那些,并与它们一起使用。这也意味着任何GroupMembershipService的子类或实现GroupMembershipService接口的东西都可以“注入”到PersonService中,PersonService不需要知道更改。

到目前为止我找到的最好的定义是詹姆斯·肖尔

“依赖注入”是25美元5美分的概念[…]依赖注入意味着给予对象其实例变量。[…]。

还有本文作者:Martin Fowler也可能被证明是有用的。

依赖注入基本上是提供对象需要的对象(其依赖项),而不是让它自己构建它们。这是一种非常有用的测试技术,因为它允许模拟或存根依赖项。

依赖可以通过多种方式注入到对象中(比如构造函数注入或setter注入)。甚至可以使用专门的依赖注入框架(例如Spring)来做到这一点,但它们肯定不是必需的。你不需要那些框架来进行依赖注入。显式实例化和传递对象(依赖)和框架注入一样好。

公认的答案是一个很好的答案——但我想补充一点,DI非常像代码中避免硬编码常量的经典做法。

当你使用像库名这样的常量时,你会快速将它从代码内部移动到某个配置文件,并将包含该值的变量传递到需要它的地方。这样做的原因是这些常量通常比其他代码更频繁地更改。例如,如果你想测试测试数据库中的代码。

DI类似于面向对象编程中的这一点。那里的值而不是常量文字是整个对象——但是将创建它们的代码从类代码中移出的原因是相似的——对象比使用它们的代码更改得更频繁。需要这种更改的一个重要情况是测试。

我发现了这个有趣的例子,在松耦合方面

来源:理解依赖注入

任何应用程序都由许多对象组成,这些对象相互协作以执行一些有用的东西。传统上,每个对象负责获取对其协作的依赖对象(依赖项)的引用。这导致了高度耦合的类和难以测试的代码。

例如,考虑一个Car对象。

Car依赖于车轮、发动机、燃料、电池等来运行。传统上,我们将此类依赖对象的品牌与Car对象的定义一起定义。

没有依赖注入(DI):

class Car{private Wheel wh = new NepaliRubberWheel();private Battery bt = new ExcideBattery();
//The rest}

在这里,Car对象负责创建依赖对象。

如果我们想在最初的NepaliRubberWheel()穿刺后更改其依赖对象的类型怎么办?我们需要重新创建Car对象及其新的依赖项,例如ChineseRubberWheel(),但只有Car制造商才能做到这一点。

那么0号能为我们做些什么呢?

当使用依赖注入时,对象会被赋予它们的依赖关系在运行时而不是编译时(汽车制造时)。因此,我们现在可以随时更改Wheel。在这里,dependencywheel)可以在运行时注入Car

使用依赖注入后:

在这里,我们在运行时是注入依赖关系(轮子和电池)。因此,术语:依赖注入。我们通常依赖DI框架,如Spring、Guice、Weld来创建依赖项并在需要时注入。

class Car{private Wheel wh; // Inject an Instance of Wheel (dependency of car) at runtimeprivate Battery bt; // Inject an Instance of Battery (dependency of car) at runtimeCar(Wheel wh,Battery bt) {this.wh = wh;this.bt = bt;}//Or we can have settersvoid setWheel(Wheel wh) {this.wh = wh;}}

其优点是:

  • 解耦对象的创建(换句话说,从对象的创建中分离使用)
  • 能够替换依赖项(例如:Wheel,Battery)而无需更改使用它的类(Car)
  • 提倡“代码到接口而不是实现”原则
  • 能够在测试期间创建和使用模拟依赖(如果我们想在测试期间使用Mock of Wheel而不是真实实例…我们可以创建Mock Wheel对象并让DI框架注入Car)

假设你想去钓鱼:

  • 没有依赖注入,你需要自己处理一切。你需要找到一艘船,买一根鱼竿,寻找诱饵,等等。当然,这是可能的,但它会让你承担很多责任。用软件术语来说,这意味着你必须对所有这些事情进行查找。

  • 通过依赖注入,其他人负责所有的准备工作,并为您提供所需的设备。您将收到(“被注入”)船、鱼竿和诱饵——所有这些都可以使用。

“依赖注入”不只是意味着使用参数化构造函数和公共setter吗?

James Shore的文章提供了以下示例进行比较

没有依赖注入的构造函数:

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();...}}

书名:“扎根Java开发人员:Java7和多语言编程的重要技术

DI是IoC的一种特殊形式,其中查找依赖项的过程是在当前正在执行的代码的直接控制之外。

依赖注入(DI)的全部意义在于保留应用程序源代码干净稳定

  • 干净的依赖初始化代码
  • 稳定不管使用的依赖

实际上,每个设计模式都将关注点分开,以使未来的更改影响最小文件。

DI的特定领域是依赖配置和初始化的委托。

示例:带有外壳脚本的DI

如果您偶尔在Java之外工作,请回想一下source在许多脚本语言中是如何经常使用的(Shell、Tcl等,甚至在Python中被滥用于此目的)。

考虑简单的dependent.sh脚本:

#!/bin/sh# Dependenttouch         "one.txt" "two.txt"archive_files "one.txt" "two.txt"

脚本是依赖的:它自己不会成功执行(archive_files未定义)。

您在archive_files_zip.sh实现脚本中定义archive_files(在本例中使用zip):

#!/bin/sh# Dependencyfunction archive_files {zip files.zip "$@"}

而不是直接在依赖脚本中source-ing实现脚本,您使用injector.sh“容器”包装两个“组件”:

#!/bin/sh# Injectorsource ./archive_files_zip.shsource ./dependent.sh

archive_files依赖刚刚被注入变成依赖脚本。

您可以使用tarxz注入实现archive_files的依赖项。

示例:删除DI

如果dependent.sh脚本直接使用依赖项,则该方法将称为依赖查找(与依赖注入相反):

#!/bin/sh# Dependent
# dependency look-upsource ./archive_files_zip.sh
touch         "one.txt" "two.txt"archive_files "one.txt" "two.txt"

现在的问题是依赖“组件”必须自己执行初始化。

“组件”的源代码既不是干净也不是稳定,因为依赖项初始化的每次更改都需要“组件”源代码文件的新版本。

最后的遗言

DI不像Java框架那样被广泛强调和推广。

但这是一种通用的方法来划分以下关注点:

  • 应用程序发展单一源代码发布生命周期)
  • 应用程序部署多个具有独立生命周期的目标环境)

仅对依赖查找使用配置没有帮助,因为每个依赖项(例如新的身份验证类型)的配置参数数量以及支持的依赖项类型(例如新的数据库类型)的数量可能会发生变化。

我想既然大家都为DI写过了,那就让我问几个问题…

  1. 当你有一个DI的配置时,所有实际的实现(不是接口)都将被注入到一个类中(例如控制器的服务),为什么这不是某种硬编码?
  2. 如果我想在运行时更改对象怎么办?例如,我的配置已经说过当我实例化MyController时,将FileLogger注入为ILogger。但我可能想注入数据库记录器。
  3. 每次我想更改我的AClass需要的对象时,我现在需要查看两个地方-类本身和配置文件。这如何让生活更轻松?
  4. 如果没有注入AClass的属性,模拟它会更难吗?
  5. 回到第一个问题。如果使用new Object()不好,为什么我们注入的是实现而不是接口?我想你们很多人都在说我们实际上是在注入接口,但配置让你指定该接口的实现…不是在运行时…它在编译时被硬编码。

这是基于@Adam N发布的答案。

为什么个人服务不再需要担心GroupMembership Service?你刚刚提到GroupM的成员资格有很多东西(对象/属性)。如果PService中需要GMService,你会把它作为一个属性。无论你是否注入它,你都可以把它模拟出来。我唯一希望注入它的时候是GMService有更具体的子类,这要到运行时才能知道。然后你会想注入子类。或者你想将其用作单例或原型。老实说,配置文件已经硬编码了一切,只要它在编译时要注入类型(接口)的子类。

编辑

一个很好的评论由Jose Maria Arranz关于DI

DI通过消除任何确定依赖方向和编写任何粘合代码的需要来增加内聚力。

错误。依赖关系的方向是XML形式或注释,您的依赖关系被编写为XML代码和注释。XML和注释是源代码。

DI通过使您的所有组件模块化(即可替换)并具有定义良好的接口来减少耦合。

错误。您不需要DI框架来构建基于接口的模块化代码。

关于可替换:使用非常简单的. Properties归档文件和Class.forName,您可以定义哪些类可以更改。如果您的代码的任何类都可以更改,Java不适合您,请使用脚本语言。顺便说一句:注释不能在不重新编译的情况下更改。

在我看来,DI框架只有一个原因:锅炉板减少。有了一个做得好的工厂系统,你可以像你喜欢的DI框架一样做同样的事情,更受控制,更可预测,DI框架承诺减少代码(XML和注释也是源代码)。问题是这种锅炉板减少在非常简单的情况下是真实的(每个类一个实例等等),有时在现实世界中选择适当的服务对象并不像将类映射到单例对象那么容易。

简单地说,依赖注入(DI)是消除不同对象之间依赖关系或紧密耦合的方法。依赖注入为每个对象提供了内聚行为。

DI是Spring的IOC原则的实现,它说“不要打电话给我们,我们会打电话给你”。使用依赖注入程序员不需要使用new关键字创建对象。

对象一旦加载到Spring容器中,然后我们通过使用getBean(String beanName)方法从Spring容器中获取这些对象来重用它们。

这意味着对象应该只具有完成其工作所需的依赖关系,并且依赖关系应该很少。此外,一个对象的依赖关系应该在接口上,而不是在“具体”对象上,如果可能的话。(具体对象是任何用关键字new创建的对象。)松耦合促进了更大的可重用性,更容易的可运维性,并允许您轻松地提供“模拟”对象来代替昂贵的服务。

“依赖注入”(DI)也称为“控制反转”(IoC),可以用作鼓励这种松散耦合的技术。

实现DI有两种主要方法:

  1. 构造函数注入
  2. Setter注射

构造函数注入

这是将对象依赖项传递给其构造函数的技术。

请注意,构造函数接受一个接口,而不是具体对象。此外,请注意,如果order道参数为空,将引发异常。这强调了接收有效依赖项的重要性。在我看来,构造函数注入是赋予对象依赖项的首选机制。开发人员在调用对象时很清楚需要将哪些依赖项赋予“Person”对象才能正确执行。

Setter注射

但考虑下面的例子……假设你有一个类有十个没有依赖项的方法,但你要添加一个依赖于IDAO的新方法。你可以将构造函数更改为使用构造函数注入,但这可能会迫使你更改所有构造函数调用。或者,你可以只添加一个接受依赖项的新构造函数,但是开发人员如何轻松知道何时使用一个构造函数而不是另一个。最后,如果创建依赖项非常昂贵,为什么要创建它并将其传递给构造函数,而它可能很少使用?“Setter注入”是另一种可用于此类情况的DI技术。

Setter注入不会强制将依赖项传递给构造函数。相反,依赖项被设置为需要的对象公开的公共属性。如前所述,这样做的主要动机包括:

  1. 支持依赖注入,而无需修改遗留类的构造函数。
  2. 允许尽可能晚地创建昂贵的资源或服务,并且仅在需要时创建。

下面是上面代码的示例:

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 insteadprivate IDAO addressdao;

依赖注入是通常称为“依赖混淆”需求的一种可能的解决方案。依赖混淆是一种将“明显”性质从向需要它的类提供依赖的过程中去除的方法,因此在某种程度上混淆了向所述类提供所述依赖的过程。这不一定是坏事。事实上,通过混淆向类提供依赖的方式,那么类之外的某些东西负责创建依赖。这意味着,在各种场景下,可以向类提供依赖的不同实现,而无需对类进行任何更改。这非常适合在生产和测试模式之间切换(例如,使用“模拟”服务依赖项)。

不幸的是,糟糕的部分是,有些人认为你需要一个专门的框架来进行依赖混淆,如果你选择不使用特定的框架来做这件事,你在某种程度上是一个“较小”的程序员。另一个非常令人不安的神话,许多人相信,依赖注入是实现依赖混淆的唯一方法。这是明显的,历史上的,显然是100%错误的,但你将很难说服一些人,对于你的依赖混淆要求,有依赖注入的替代方案。

多年来,程序员已经理解了依赖混淆的要求,并且在考虑依赖注入之前和之后,已经发展了许多替代解决方案。有工厂模式,但也有许多使用ThreadLocal的选项,其中不需要对特定实例进行注入-依赖被有效地注入到线程中,其好处是使对象可用(通过方便的静态getter方法)任何类,该类需要它,而无需向需要它的类添加注释并设置复杂的XML“胶水”来实现它。当持久性(JPA/JDO或其他)需要依赖项时,它允许您更轻松地实现“透明持久性”,并且使用纯粹由POJO组成的域模型和业务模型类(即没有特定于框架/锁定在注释中)。

依赖注入是与Spring框架相关的概念的核心。在创建任何项目的框架时,Spring都可以发挥至关重要的作用,这里依赖注入是投手。

实际上,假设在java中你创建了两个不同的类,分别是class A和class B,无论class B中有什么函数你都想在class A中使用,所以那时可以使用依赖注入。您可以将一个类的对象放在另一个类中,就像您可以将整个类注入另一个类中以使其可访问一样。通过这种方式可以克服依赖性。

依赖性注射是简单地粘合两种类型,同时将它们分开。

我知道已经有很多答案了,但我发现这很有帮助: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实例的任何类提供。

什么是依赖注入(DI)?

正如其他人所说,依赖注入(DI)消除了直接创建和管理我们感兴趣的类(消费者类)依赖的其他对象实例(在UML感中)的生命周期的责任。相反,这些实例被传递给我们的消费者类,通常作为构造函数参数或通过属性设置器(依赖对象实例化和传递给消费者类的管理通常由控制反转(IoC)容器执行,但这是另一个主题)。

DI、DIP和SOLID

具体来说,在Robert C Martin的面向对象设计的SOLID原则范式中,DI依赖倒置原则(DIP)的可能实现之一。DIP是#2咒语中的#1-其他DIP实现包括服务定位器和插件模式。

DIP的目标是解耦类之间紧密的、具体的依赖关系,而是通过抽象来放松耦合,这可以通过interfaceabstract classpure virtual class来实现,具体取决于所使用的语言和方法。

如果没有DIP,我们的代码(我称之为“消费类”)直接耦合到一个具体的依赖项,并且还经常承担着知道如何获取和管理这种依赖项的实例的责任,即概念上:

"I need to create/use a Foo and invoke method `GetBar()`"

而在应用DIP之后,需求被放松了,并且不再需要担心获取和管理Foo依赖项的生命周期:

"I need to invoke something which offers `GetBar()`"

为什么使用DIP(和DI)?

通过这种方式解耦类之间的依赖关系允许这些依赖类中的容易替代与其他也满足抽象先决条件的实现(例如依赖可以与相同接口的另一个实现切换)。此外,正如其他人提到的,通过DIP解耦类的最常见原因可能是允许消费类被隔离测试,因为这些相同的依赖现在可以被存根和/或模拟。

DI的一个后果是依赖对象实例的生命周期管理不再由消费类控制,因为依赖对象现在被传递到消费类中(通过构造函数或setter注入)。

这可以用不同的方式来看待:

  • 如果需要保留消费类对依赖项的生命周期控制,可以通过向消费类注入用于创建依赖类实例的(抽象)工厂来重新建立控制。消费者将能够根据需要通过工厂上的Create获取实例,并在完成后处理这些实例。
  • 或者,可以将依赖实例的生命周期控制放弃给IoC容器(下面详细介绍)。

什么时候使用DI?

  • 如果可能需要用依赖替换等效的实现,
  • 任何时候你需要单元测试一个类的方法,
  • 依赖生命周期的不确定性可能需要进行实验(例如,嘿,MyDepClass是线程安全的——如果我们将其设为单例并将相同的实例注入所有消费者怎么办?)

示例

下面是一个简单的C#实现。

public class MyLogger{public void LogRecord(string somethingToLog){Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);}}

虽然看似无害,但它对另外两个类System.DateTimeSystem.Console有两个static依赖项,这不仅限制了日志输出选项(如果没有人监视,则记录到控制台将毫无价值),而且更糟糕的是,由于依赖于非确定性系统时钟,很难自动测试。

但是,我们可以将DIP应用于这个类,方法是将时间戳的关注点抽象为依赖项,并仅将MyLogger耦合到一个简单的接口:

public interface IClock{DateTime Now { get; }}

我们还可以将对Console的依赖放松为抽象,例如TextWriter。依赖注入通常实现为constructor注入(将抽象作为参数传递给依赖项作为消费类的构造函数)或Setter Injection注入(通过setXyz()设置器或定义{set;}的. Net属性传递依赖项)。构造函数注入是首选,因为这可以保证类在构造后处于正确的状态,并允许内部依赖字段标记为readonly(C#)或final(Java)。因此,在上面的例子中使用构造函数注入,这给我们留下了:

public class MyLogger : ILogger // Others will depend on our logger.{private readonly TextWriter _output;private readonly IClock _clock;
// Dependencies are injected through the constructorpublic 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,当然可以恢复为DateTime.Now,并且两个依赖项需要由IoC容器通过构造函数注入提供)

可以构建一个自动化的单元测试,这明确证明我们的记录器工作正常,因为我们现在可以控制依赖项-时间,并且我们可以监视书面输出:

[Test]public void LoggingMustRecordAllInformationAndStampTheTime(){// Arrangevar mockClock = new Mock<IClock>();mockClock.Setup(c => c.Now).Returns(new DateTime(2015, 4, 11, 12, 31, 45));var fakeConsole = new StringWriter();
// Actnew MyLogger(fakeConsole, mockClock.Object).LogRecord("Foo");
// AssertAssert.AreEqual("2015-04-11 12:31:45 - Foo", fakeConsole.ToString());}

后续步骤

依赖注入总是与控制反转容器(IoC)相关联,以注入(提供)具体的依赖实例,并管理生命周期实例。在配置/无融资创业过程中,IoC容器允许定义以下内容:

  • 每个抽象和配置的具体实现之间的映射(例如"每当消费者请求#0时,返回#1实例"
  • 可以为每个依赖项的生命周期管理设置策略,例如为每个消费者实例创建一个新对象,在所有消费者之间共享单例依赖项实例,仅在同一线程之间共享相同的依赖项实例等。
  • 在. Net中,IoC容器了解IDisposable等协议,并将根据配置的生命周期管理承担Disposing依赖项的责任。

通常,一旦配置/引导了IoC容器,它们就会在后台无缝运行,从而允许编码器专注于手头的代码,而不用担心依赖关系。

DI友好代码的关键是避免类的静态耦合,并且不要使用new()来创建依赖项

如上例所示,依赖关系的解耦确实需要一些设计工作,对于开发人员来说,需要进行范式转换,以打破直接new依赖关系的习惯,而不是信任容器来管理依赖关系。

但是好处很多,特别是在彻底测试你的兴趣类别的能力方面。

说明:创建/映射/投影(通过new ..())POCO/POJO/序列化DTO/实体图/匿名JSON投影等-即“仅限数据”类或记录-使用或从方法返回被没有视为依赖关系(在UML意义上),不受DI的约束。使用new来投影这些很好。

流行的答案没有帮助,因为它们以一种无用的方式定义了依赖注入。让我们同意,“依赖”是指我们的对象X需要的一些预先存在的其他对象。但是当我们说

$foo = Foo->new($bar);

我们称之为将参数传递给构造函数。自从构造函数被发明以来,我们一直在定期这样做。

“依赖注入”被认为是一种“控制反转”,这意味着从调用者中取出一些逻辑。调用者传入参数时并非如此,因此如果是DI,DI不会暗示控制反转。

DI意味着调用者和管理依赖关系的构造函数之间有一个中间层。Makefile是依赖注入的一个简单示例。“调用者”是在命令行上键入“make bar”的人,“构造函数”是编译器。Makefile指定bar依赖于foo,它执行一个

gcc -c foo.cpp; gcc -c bar.cpp

在做一个

gcc foo.o bar.o -o bar

输入“make bar”的人不需要知道bar依赖于foo。依赖关系是在“make bar”和gcc之间注入的。

中间级的主要目的不仅仅是将依赖项传递给构造函数,而是列出只有一个地方中的所有依赖项,并将它们隐藏在编码器中(而不是让编码器提供它们)。

通常中间层为构造对象提供工厂,而工厂必须提供每种请求的对象类型都必须满足的角色。这是因为通过拥有隐藏构造细节的中间层,你已经遭受了工厂强加的抽象惩罚,所以你还不如使用工厂。

来自BookApress.Spring.Persistence.with.2010年10月

依赖注入的目的是解耦从您的应用程序业务中解析外部软件组件逻辑。如果没有依赖注入,组件的细节如何访问所需的服务可能会与组件的代码。这不仅增加了错误的可能性,还增加了代码膨胀,并放大维护复杂性;它耦合组件更紧密地结合在一起,使得在###重构或测试。

依赖注入(DI)来自设计模式,它使用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 cmint pages = 0; // number of pages
/** constructor */public FictionBook() {// TODO Auto-generated constructor stub}
@Overridepublic FictionBook setHeight(int height) {this.height = height;return this;}
@Overridepublic FictionBook setPages(int pages) {this.pages = pages;return this;}
@Overridepublic int getHeight() {// TODO Auto-generated method stubreturn height;}
@Overridepublic int getPages() {// TODO Auto-generated method stubreturn pages;}
@Overridepublic 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 Ipublic void setBook(BookInterface book) {this.book = book;}
// injection IIpublic BookInterface setBook(String bookName) {try {Class<?> cl = Class.forName(bookName);Constructor<?> constructor = cl.getConstructor(); // use it for parameters in constructorBookInterface 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 IFictionBook bookI = new FictionBook();bookI.setHeight(30); // cmbookI.setPages(250);ab.setBook(bookI); // injectSystem.out.println("injection I " + ab.getBook().toString());
// injection IIFictionBook bookII = ((FictionBook) ab.setBook("com.deepam.hidden.FictionBook")).setHeight(5).setPages(108); // inject and setSystem.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中,它是通过创建和初始化申请信息容器来完成的,它为我们完成这项工作。我们只需在代码中创建Context并调用初始化bean。在那一刻注入已经自动完成。

什么是依赖注入?

依赖注入(DI)的意思是将相互依赖的对象解耦。假设对象A依赖于对象B,所以我们的想法是将这些对象彼此解耦。我们不需要使用new关键字对对象进行硬编码,而是在运行时共享对对象的依赖关系,尽管有编译时。如果我们谈论

依赖注入在Spring中是如何工作的:

我们不需要使用new关键字对对象进行硬编码,而是在配置文件中定义bean依赖项。Spring容器将负责连接所有。

控制反转(IOC)

IOC是一个通用的概念,它可以用许多不同的方式表达,依赖注入是IOC的一个具体例子。

两种类型的依赖注入:

  1. 构造函数注入
  2. Setter注射

1.基于构造函数的依赖注入:

基于构造函数的DI是在容器调用具有多个参数的类构造函数时完成的,每个参数表示对其他类的依赖关系。

public class Triangle {
private String type;
public String getType(){return type;}
public Triangle(String type){   //constructor injectionthis.type=type;}}<bean id=triangle" class ="com.test.dependencyInjection.Triangle"><constructor-arg value="20"/></bean>

2.基于setter的依赖注入:

基于Setter的DI是通过容器在调用无参数构造函数或无参数静态工厂方法来实例化bean后调用bean上的setter方法来完成的。

public class Triangle{
private String type;
public String getType(){return type;}public void setType(String type){          //setter injectionthis.type = type;}}
<!-- setter injection --><bean id="triangle" class="com.test.dependencyInjection.Triangle"><property name="type" value="equivialteral"/>

注意:对于强制依赖项使用构造函数参数,对于可选依赖项使用setter,这是一个很好的经验法则。请注意,如果我们在setter上使用基于@所需注释的注释,可以将setter作为必需依赖项。

依赖注入意味着(实际上是任何方式)代码的一部分(例如一个类)可以以模块化的方式访问依赖项(代码的其他部分,例如其他类,它取决于),而不需要硬编码(因此它们可以根据需要自由更改或覆盖,甚至在另一个时间加载)

(和ps,是的,它已经成为一个相当简单的概念的过度炒作的25$名称),我的.25美分

我能想到的最好的类比是手术室里的外科医生和他的助手,外科医生是主要的人,他的助手在他需要的时候提供各种外科部件,这样外科医生就可以专注于他最擅长的一件事(手术)。没有助手,外科医生每次需要部件时都必须自己拿。

DI简而言之,是一种通过向组件提供依赖组件来消除组件上的常见附加责任(负担)的技术。

DI使您更接近单一职责(SR)原则,例如surgeon who can concentrate on surgery

何时使用DI:我建议在几乎所有的生产项目(小/大)中使用DI,特别是在不断变化的业务环境中:)

为什么:因为你希望你的代码易于测试,可嘲笑等,这样你就可以快速测试你的更改并将其推向市场。此外,当你有很多很棒的免费工具/框架来支持你进入代码库的旅程时,你为什么不呢?

这个是我见过的关于依赖注入依赖注入容器的最简单的解释:

没有依赖注入

  • 应用程序需要Foo(例如控制器),因此:
  • 应用程序创建Foo
  • 应用程序调用Foo
    • Foo需要Bar(例如服务),所以:
    • Foo创建Bar
    • Foo呼叫Bar
      • Bar需要Bim(一个服务,一个存储库,…),所以:
      • 酒吧创造Bim
      • 酒吧是有作用的

依赖注入

  • 应用程序需要Foo,它需要Bar,它需要Bim,所以:
  • 应用程序创建Bim
  • 应用程序创建Bar并赋予它Bim
  • 应用程序创建Foo并赋予它Bar
  • 应用程序调用Foo
    • Foo呼叫Bar
      • 酒吧是有作用的

使用依赖注入容器

  • 应用程序需要Foo:
  • 应用程序从容器中获取Foo,因此:
    • 容器创建Bim
    • 容器创建Bar并赋予它Bim
    • 容器创建Foo并给它Bar
  • 应用程序调用Foo
    • Foo呼叫Bar
      • 酒吧是有作用的

依赖注入依赖注入容器是不同的:

  • 依赖注入是一种编写更好代码的方法
  • DI容器是一个帮助注入依赖关系的工具

您不需要容器来进行依赖注入。但是容器可以帮助您。

依赖注入是依赖反转原则实践的一部分,也称为控制反转(IoC)。基本上,你需要进行依赖反转原则,因为你想让你的代码更加模块化和可单元测试,而不仅仅是一个单一的系统。所以你开始识别可以从类中分离出来并抽象出来的代码部分。现在需要从类之外注入抽象的实现。通常这可以通过构造函数完成。所以你创建了一个接受抽象作为参数的构造函数,这称为依赖注入(通过构造函数)。关于DIP、DI和IoC容器的更多解释,你可以阅读这里

为了使依赖注入概念易于理解,让我们以切换按钮切换(打开/关闭)灯泡为例。

没有依赖注入

交换机需要事先知道我连接到哪个灯泡(硬编码依赖)。所以,

开关->PermanentBulb//开关直接连接到永久灯泡,测试不容易

Switch(){PermanentBulb = new Bulb();PermanentBulb.Toggle();}

依赖注入

开关只知道我需要打开/关闭传给我的灯泡。所以,

开关->Bulb1 OR Bulb2 OR Night Bulb(注入依赖)

Switch(AnyBulb){ //pass it whichever bulb you likeAnyBulb.Toggle();}

修改开关和灯泡的詹姆斯示例:

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();...}}`

依赖注入是基于框架构建的“控制权倒置”原则的一种实现。

如GoF的“设计模式”中所述,框架是实现主控制流逻辑的类,由开发人员来实现,这样框架就实现了控制原理的反转。

作为一种技术而不是类层次结构来实现的一种方式,这种IoC原则只是依赖注入。

DI主要包括将类实例的映射和对该实例的类型引用委托给外部“实体”:对象、静态类、组件、框架等…

类实例是“依赖关系”,调用组件与类实例的外部绑定通过引用它是“注入”。

显然,您可以从OOP的角度以多种方式实现这种技术,例如构造函数注入setter注入界面注入

当您想将需要某些服务的组件与相同的服务实现完全分离时,委托第三方执行将ref与对象匹配的任务非常有用。

通过这种方式,在设计组件时,您可以专注于它们的架构和特定逻辑,信任与其他对象协作的接口,而不用担心所使用的对象/服务的任何类型的实现更改,也不用担心您正在使用的同一对象是否会被完全替换(显然尊重接口)。

任何重要的应用程序都由两个或多个类组成,这些类相互协作以执行一些业务逻辑。传统上,每个对象负责获取对其协作对象(其依赖项)的自己的引用。应用DI时,对象在创建时由一些协调系统中每个对象的外部实体赋予它们的依赖关系。换句话说,依赖项被注入到对象中。

详情请见在此处输入链接描述

让我们尝试使用汽车引擎类的简单示例,任何汽车都需要发动机才能去任何地方,至少现在是这样。所以下面是没有依赖注入的代码的外观。

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紧密耦合的这段代码的问题,如果我们决定将其更改为Electric yEngine,那么我们将需要重写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或Electric yEngine。这就是依赖注入的用武之地。

   Car gasCar = new Car(new GasEngine());gasCar.Run();Car electroCar = new Car(new ElectricityEngine());electroCar.Run();

这里我们基本上将我们的依赖项(引擎实例)注入(传递)到Car构造函数。所以现在我们的类在对象及其依赖项之间具有松耦合,我们可以轻松添加新类型的引擎而无需更改Car类。

依赖注入的主要好处是类更松耦合,因为它们没有硬编码的依赖项。这遵循了上面提到的依赖反转原则。类请求抽象(通常是接口),而不是引用特定的实现,这些抽象是在构造类时提供给它们的。

所以最后依赖注入只是一种技术实现对象及其依赖关系之间的松耦合。而不是直接实例化类需要的依赖项为了执行其操作,将依赖项提供给类(大多数情况下)通过构造函数注入。

此外,当我们有许多依赖项时,使用控制反转(IoC)容器是非常好的做法,我们可以告诉哪些接口应该映射到我们所有依赖项的哪些具体实现,并且我们可以让它在构造对象时为我们解决这些依赖项。例如,我们可以在IoC容器的映射中指定iEngine依赖项应该映射到GasEngine类,当我们向IoC容器询问汽车类的实例时,它会自动构造我们的汽车类,并传入GasEngine依赖项。

更新:最近看了Julie Lerman关于EF Core的课程,也喜欢她对DI的简短定义。

依赖注入是一种允许您的应用程序注入的模式对象飞到需要它们的类,而不会强迫那些类负责这些对象。它允许您的代码更松散耦合,并且Entity Framework Core插入到相同的服务系统

以上所有的答案都很好我的目的是用一种简单的方式来解释这个概念以便任何没有编程知识的人也能理解这个概念

依赖注入是帮助我们以更简单的方式创建复杂系统的设计模式之一。

我们可以在日常生活中看到这种模式的各种应用。一些例子是录音机,VCD,CD驱动器等。

卷到卷便携式磁带录音机,20世纪中期。

上图是20世纪中期卷轴到卷轴便携式磁带录音机的图像。来源

录音机的主要目的是记录或回放声音。

在设计一个系统时,它需要一个卷轴来记录或回放声音或音乐。设计这个系统有两种可能性

  1. 我们可以把卷轴放进机器里
  2. 我们可以为卷轴提供一个可以放置它的钩子。

如果我们使用第一个,我们需要打开机器来更换卷轴。如果我们选择第二个,那就是为卷轴放置一个钩子,我们通过改变卷轴来播放任何音乐的额外好处。

就像明智的依赖注入一样,依赖注入是将依赖外部化的过程,只关注组件的特定功能,这样独立的组件就可以耦合在一起形成一个复杂的系统。

我们通过使用依赖注入实现的主要好处。

  • 高内聚和松耦合。
  • 外化依赖,只看责任。
  • 将事物作为组件进行组合,以形成具有高功能的大型系统。
  • 它有助于开发高质量的组件,因为它们是独立开发的,它们经过了适当的测试。
  • 如果一个组件失败,它有助于用另一个组件替换。

现在,这些概念构成了编程世界中众所周知的框架的基础。Spring Angular等是建立在这个概念之上的著名软件框架

依赖注入是一种模式,用于创建其他对象依赖的对象实例,而在编译时不知道将使用哪个类来提供该功能,或者简单地将属性注入对象的方式称为依赖注入。

依赖注入示例

以前我们是这样写代码的

Public MyClass{DependentClass dependentObject/*At somewhere in our code we need to instantiatethe 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();}

你也可以阅读

控制反转和依赖注入的区别

例如,我们有2个类ClientServiceClient将使用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构造函数时,我们需要在所有地方更改代码,创建Service对象

使用依赖注入

方式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匕首2

优势

  • 让测试更简单
  • 当您更改Service时,您只需要在Injector类中更改它
  • 如果您使用useConstructor Injection,当您查看Client的构造函数时,您将看到Client类有多少依赖项

缺点

  • 如果你使用useConstructor InjectionService对象是在Client创建时创建的,有时我们在Client类中使用函数而不使用Service,所以创建的Service被浪费了

依赖注入定义

https://en.wikipedia.org/wiki/Dependency_injection

依赖项是可以使用的对象(Service
注入是将依赖项(Service)传递给将使用它的依赖对象(Client

摘自克里斯托弗·诺林,Pablo Deeleman的书“Learning Angular-第二版”:

“随着我们的4.4应用项目的增长和发展,我们的代码实体中的每一个都将在内部需要其他对象的实例,在软件工程领域,其他对象的实例被更好地称为依赖关系。将这样的依赖关系传递给依赖客户端的行动被称为注射,它还需要另一个代码实体的参与,名为注射器注射器将负责实例化代码实体0所需的依赖关系,因此它们从成功注入客户端的那一刻起就可以使用。这非常重要,因为客户端对如何代码实体2自己的依赖关系一无所知,并且只知道他们为了使用它们而实现的代码实体4。”

摘自:Anton Moiseev。书“使用打字稿进行角度开发,第二版。”:

“简而言之,DI帮助您以松耦合的方式编写代码,并使您的代码更像可测试可重复使用。”

我会提出一个稍微不同的,简短而精确的依赖注入定义,专注于主要目标,而不是技术手段(从这里开始):

依赖注入是创建静态、无状态的过程服务对象图,其中每个服务由其依赖关系。

我们在应用程序中创建的对象(无论我们使用Java,C#或其他面向对象的语言)通常分为两类:无状态,静态和全局“服务对象”(模块),以及有状态,动态和本地“数据对象”。

模块图——服务对象的图——通常是在应用程序启动时创建的。这可以使用容器来完成,例如Spring,但也可以通过将参数传递给对象构造函数来手动完成。这两种方式都有其优点和缺点,但框架绝对不是在应用程序中使用DI所必需的。

一个要求是服务必须由它们的依赖项参数化。这意味着什么完全取决于给定系统中采用的语言和方法。通常,这采取构造函数参数的形式,但使用setter也是一种选择。这也意味着服务的依赖项对服务用户是隐藏的(在调用服务方法时)。

什么时候使用?我会说,只要应用程序足够大,可以将逻辑封装到单独的模块中,模块之间的依赖关系图就会增加代码的易读性和可探索性。

依赖注入是使分离的组件与它们的一些依赖无关的实践,这遵循固体指南,即

依赖倒置原则:应该“依赖抽象,不结实

依赖注入更好的实现是组合根设计模式,因为它允许您的组件与依赖注入容器解耦。

我推荐这篇关于组合根的好文章http://blog.ploeh.dk/2011/07/28/CompositionRoot/作者:Mark Seemann

以下是这篇文章的要点:

组合根是应用程序中(最好)唯一的位置其中模块组合在一起。

只有应用程序应该有组合根。库和框架不应该。

DI容器只能从组合根引用。所有其他模块都不应该引用容器。

依赖注入框架Di-Ninja的留档是一个很好的例子,它演示了组合根和依赖注入的原理。https://github.com/di-ninja/di-ninja据我所知,是JavaScript中唯一一个实现组合根设计模式的DiC。

DI是真实对象如何相互作用,而不会让一个对象对另一个对象的存在负责。对象应该被平等对待。它们都是对象。没有人应该像创造者一样行事。这就是你如何公平对待你的对象。

简单的例子

如果你需要一个医生,你只需去找(现有的)一个。你不会考虑从头开始创造一个医生来帮助你。他已经存在了,他可能为你或其他物体服务。无论你(一个物体)是否需要他,他都有权存在,因为他的目的是为一个或多个物体服务。决定他存在的是全能的上帝,而不是自然选择。因此,DI的一个优点是避免在你的宇宙(即应用程序)的生命周期内创造无用的冗余物体,没有目的。

5岁儿童的依赖注射。

当你自己从冰箱里拿东西时,你可能会遇到麻烦。你可能会让门开着,你可能会得到妈妈或爸爸不想让你拥有的东西。你甚至可能会寻找我们甚至没有或已经过期的东西。

你应该做的是陈述需求,“我需要在午餐时喝点东西,”然后我们会确保你坐下来吃饭时有东西。

我们可以实现依赖注入来知道它:

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的实现

在进行技术描述之前,首先用一个真实的例子来可视化它,因为你会发现很多学习依赖注入的技术内容,但大多数人都无法了解它的核心概念。

在第一张图片中,假设你有一个汽车厂有很多单位。一辆车实际上是在组装单元中建造的,但它需要引擎座位车轮。所以组装单元依赖于所有这些单位,它们是工厂的依赖关系

你可以感觉到,现在维护这个工厂的所有任务都太复杂了,因为除了主要任务(在组装单元组装汽车)之外,你还必须专注于其他单位。现在维护成本很高,厂房很大,所以需要额外的钱来支付租金。

现在,看看第二张图片。如果你找到一些供应商公司,他们会以比你自己生产成本更便宜的价格为你提供车轮座位引擎,那么现在你不需要在你的工厂制造它们。你现在可以租一个较小的建筑,只是为了你的组装单元,这将减少你的维护任务,减少你的额外租金成本。现在你也可以只专注于你的主要任务(汽车组装)。

现在我们可以说,组装汽车的所有依赖关系都是提供商工厂的注入。这是现实生活中依赖注入(DI)的一个例子。

用专业术语来说,依赖注入是一种技术,通过这种技术,一个对象(或静态方法)提供另一个对象的依赖关系。因此,将创建对象的任务转移给其他人并直接使用依赖关系的任务称为依赖注入。

这个将通过技术解释帮助您学习DI。这个将显示何时使用DI以及何时应该使用没有

所有在一个汽车工厂

简易汽车工厂