关于设计模式:什么是依赖注入?

What is dependency injection?

已经有几个关于依赖注入的问题发布了,比如什么时候使用它以及它有什么框架。然而,

什么是依赖注入,什么时候/为什么应该或不应该使用它?


到目前为止,我发现的最好的定义是詹姆斯·肖尔的定义:

"Dependency Injection" is a 25-dollar
term for a 5-cent concept. [...]
Dependency injection means giving an
object its instance variables. [...].

马丁·福勒的一篇文章也可能被证明是有用的。

依赖注入基本上是提供对象需要的对象(它的依赖项),而不是让它自己构造它们。对于测试来说,这是一种非常有用的技术,因为它允许对依赖项进行模拟或删除。

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


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

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

SomeClass()的构造方法如下:

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

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

替代方案:

  • myObject作为参数传入构造函数
1
2
3
public SomeClass (MyClass myObject) {
    this.myObject = myObject;
}

可以直接通过myObject,使测试更加容易。

  • 一种常见的选择是定义一个不做任何事情的构造函数。依赖注入可以通过setter完成。(H/T@米凯维拉)。
  • MartinFowler记录了第三种选择(h/t@marcdix),其中类显式地为程序员希望注入的依赖项实现接口。

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

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


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

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

例如,考虑一个Car对象。

Car依靠车轮、发动机、燃料、电池等来运行。传统上,我们定义依赖对象的品牌,同时定义Car对象。

无依赖注入(DI):

1
2
3
4
5
6
class Car{
  private Wheel wh = new NepaliRubberWheel();
  private Battery bt = new ExcideBattery();

  //The rest
}

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

如果我们想在最初的NepaliRubberWheel()穿刺之后改变它的依赖对象的类型,比如Wheel?我们需要用它的新依赖项重新创建汽车对象,比如说ChineseRubberWheel(),但是只有Car制造商可以这样做。

那么,我们为什么要……?

当使用依赖注入时,对象在运行时而不是在编译时(汽车制造时)得到它们的依赖性。这样我们就可以随时更改Wheel。在这里,可以在运行时将dependency(Wheel)注入Car

使用依赖注入后:

这里,我们在运行时注入依赖项(车轮和电池)。因此,术语:依赖注入。

1
2
3
4
5
6
7
8
9
10
11
12
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;
  }
}

来源:了解依赖注入


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

例如,考虑这些类别:

1
2
3
4
5
6
7
8
9
10
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构造函数,而不是在PersonService中实例化GroupMembershipService,或者添加一个属性(getter和setter)来设置它的本地实例。这意味着你的PersonService不再需要担心如何创建GroupMembershipService,它只接受它提供的,并与它们一起工作。这也意味着任何属于GroupMembershipService子类或实现GroupMembershipService接口的东西都可以"注入"到PersonService中,而PersonService不需要知道这个变化。


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

当您使用一些常量(如数据库名称)时,您可以将它从代码内部快速移动到某个配置文件中,并将包含该值的变量传递到需要它的地方。这样做的原因是,这些常量的更改频率通常比代码的其余部分要高。例如,如果您想在测试数据库中测试代码。

在面向对象编程的世界中,DI与此类似。那里的值而不是常量文本是整个对象——但是从类代码中创建它们的代码的移动原因是相似的——对象的更改比使用它们的代码更频繁。一个需要这种改变的重要案例是测试。


让我们用汽车和引擎类来尝试一个简单的例子,任何汽车都需要一个引擎去任何地方,至少现在是这样。下面是没有依赖注入的代码的外观。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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类,我们将使用下一个代码:

1
Car car = new Car();

这个代码的问题是我们与GasEngine紧密耦合,如果我们决定将其更改为ElectricityEngine,那么我们将需要重写Car类。应用程序越大,我们就越需要添加和使用新类型的引擎。

换句话说,使用这种方法,我们的高级汽车类依赖于较低级别的汽油发动机类,这违反了固体的依赖倒置原则(DIP)。DIP建议我们应该依赖抽象,而不是具体的类。为了满足这一点,我们引入了IEngine接口并重写了如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    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接口,而不是引擎的特定实现。现在,唯一的诀窍是我们如何创建汽车的实例,并给它一个实际的具体引擎类,如GasEngine或ElectricityEngine。这就是依赖注入的切入点。

1
2
3
4
   Car gasCar = new Car(new GasEngine());
   gasCar.Run();
   Car electroCar = new Car(new ElectricityEngine());
   electroCar.Run();

在这里,我们基本上将依赖项(引擎实例)注入(传递)到汽车构造函数。因此,现在我们的类在对象和它们的依赖关系之间具有松散耦合,我们可以轻松地添加新类型的引擎,而不必更改car类。

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

So in the end Dependency injection is just a technique for
achieving loose coupling between objects and their dependencies.
Rather than directly instantiating dependencies that class needs in
order to perform its actions, dependencies are provided to the class
(most often) via constructor injection.

另外,当我们有许多依赖项时,使用反转控制(IOC)容器是非常好的实践,我们可以告诉哪些接口应该映射到我们所有依赖项的具体实现,并且我们可以让它在构造对象时为我们解析这些依赖项。例如,我们可以在IOC容器的映射中指定将IEngine依赖项映射到GasEngine类,当我们向IOC容器请求Car类的实例时,它将自动构造我们的Car类,并传入GasEngine依赖项。

更新:最近看了朱莉·勒曼关于英孚核心的课程,也很喜欢她关于DI的简短定义。

Dependency injection is a pattern to allow your application to inject
objects on the fly to classes that need them, without forcing those
classes to be responsible for those objects. It allows your code to be
more loosely coupled, and Entity Framework Core plugs in to this same
system of services.


假设你想去钓鱼:

  • 没有依赖注入,你需要自己处理所有事情。你需要找一条船,买一根鱼竿,寻找鱼饵等等。当然,这是可能的,但这会给你很大的责任。在软件术语中,这意味着必须对所有这些内容执行查找。

  • 通过依赖注入,其他人负责所有的准备工作,并为您提供所需的设备。你将得到("被注射")船,鱼竿和诱饵-所有准备使用。


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

无依赖注入

  • 应用程序需要foo(例如控制器),因此:
  • 应用程序创建foo
  • 应用程序调用foo
    • FOO需要酒吧(例如服务),因此:
    • FO创建条
    • FO调用条
      • BAR需要BIM(一个服务,一个存储库,…),所以:
      • BAR创建BIM
      • 酒吧做点什么

依赖注入

  • 应用需要foo,需要bar,需要bim,所以:
  • 应用程序创建BIM
  • 应用程序创建BAR并给出BIM
  • 应用程序创建foo并赋予它条
  • 应用程序调用foo
    • FO调用条
      • 酒吧做点什么

使用依赖注入容器

  • 应用程序需要foo,因此:
  • 应用程序从容器获取foo,因此:
    • 容器创建BIM
    • 容器创建条,并给它BIM
    • 容器创建foo并赋予它bar
  • 应用程序调用foo
    • FO调用条
      • 酒吧做点什么

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

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

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


"依赖注入"不是仅仅意味着使用参数化构造函数和公共设置器吗?

JamesShore的文章展示了以下比较示例。

不带依赖项注入的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Example {
  private DatabaseThingie myDatabase;

  public Example() {
    myDatabase = new DatabaseThingie();
  }

  public void doStuff() {
    ...
    myDatabase.getData();
    ...
  }
}

具有依赖项注入的构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Example {
  private DatabaseThingie myDatabase;

  public Example(DatabaseThingie useThisDatabaseInstead) {
    myDatabase = useThisDatabaseInstead;
  }

  public void doStuff() {
    ...
    myDatabase.getData();
    ...
  }
}


什么是依赖注入(DI)?好的。

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

DI、DIP和固体好的。

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

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

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

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

然而,在应用DIP后,要求被放宽,获得和管理Foo依赖项的寿命的问题被消除:好的。

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

为什么要使用DIP(和DI)?好的。

以这种方式分离类之间的依赖关系允许用其他实现轻松替换这些依赖关系类,这些实现也满足抽象的先决条件(例如,依赖关系可以与同一接口的另一个实现交换)。此外,正如其他人所提到的,通过DIP分离类的最常见原因可能是允许独立地测试消费类,因为这些相同的依赖项现在可以被存根化和/或模拟。好的。

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

这可以用不同的方式查看:好的。

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

何时使用DI?好的。

  • 如果可能需要用依赖项替代等效的实现,
  • 任何时候,如果需要在独立于类的依赖项的情况下对类的方法进行单元测试,
  • 依赖关系寿命的不确定性可能需要实验(例如,hey,MyDepClass是线程安全的——如果我们将它设置为一个单例,并向所有消费者注入相同的实例呢?)

例子好的。

这是一个简单的C实现。考虑到以下消费类别:好的。

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

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

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

1
2
3
4
public interface IClock
{
    DateTime Now { get; }
}

我们还可以将对Console的依赖性放宽到抽象,例如TextWriter。依赖注入通常实现为constructor注入(将抽象作为参数传递给消费类的构造函数)或Setter Injection(通过setXyz()setter传递依赖项或定义了{set;}的.NET属性)。构造函数注入是首选的,因为这保证了类在构造之后将处于正确的状态,并且允许内部依赖字段被标记为EDCOX1×6(Cα)或EDCOX1×7(Java)。因此,在上面的示例中使用构造函数注入,我们可以得到:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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,当然可以恢复到DateTime.Now,这两个依赖项需要由一个ioc容器通过构造函数注入提供)好的。

可以构建一个自动化的单元测试,这最终证明我们的记录器工作正常,因为我们现在可以控制依赖关系-时间,并且我们可以监视写入的输出:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[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)关联,注入(提供)具体的依赖实例,并管理寿命实例。在配置/引导过程中,IoC容器允许定义以下内容:好的。

  • 每个抽象和配置的具体实现之间的映射(例如,"任何时候消费者请求IBar,返回ConcreteBar实例")。
  • 可以为每个依赖项的寿命管理设置策略,例如为每个使用者实例创建一个新对象、在所有使用者中共享一个单实例依赖项实例、仅在同一线程中共享同一个依赖项实例等。
  • 在.NET中,IOC容器了解诸如IDisposable之类的协议,并将根据配置的寿命管理承担Disposing依赖项的责任。

通常,一旦配置/引导了IOC容器,它们就可以在后台无缝地运行,从而使编码人员能够专注于手头的代码,而不是担心依赖性。好的。

The key to DI-friendly code is to avoid static coupling of classes, and not to use new() for the creation of Dependencies

Ok.

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

但好处是很多,尤其是在能力,彻底测试你的兴趣阶层。好的。

注:poco/pojo/serialization dtos/entity graphs/anonymous json projects et al-即"仅数据"类或记录的创建/映射/投影(通过new ..())不被视为依赖(在UML意义上),也不受DI的约束。用new来投射这些是很好的。好的。好啊。


使依赖注入概念简单易懂。让我们以开关按钮为例来切换(开/关)灯泡。

无依赖注入

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

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

></P></p>
<div class=

1
2
3
4
Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}

依赖注入

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

开关->灯泡1或灯泡2或灯泡(注入依赖)

></P></p>
<div class=

1
2
3
Switch(AnyBulb){ //pass it whichever bulb you like
AnyBulb.Toggle();
}

修改开关和灯泡的james示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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();
    ...
  }
}`

依赖注入(DI)的关键是保持应用程序源代码的干净和稳定:

  • 清除依赖项初始化代码
  • 无论使用何种依赖项,都是稳定的

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

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

示例:带shell脚本的DI

如果您偶尔在Java之外工作,请回忆一下在许多脚本语言(shell、TCL等,或者甚至在Python中误用用于此目的的EDCOX1×1)中经常使用EDOCX1 0Ω。

考虑简单的dependent.sh脚本:

1
2
3
4
#!/bin/sh
# Dependent
touch        "one.txt""two.txt"
archive_files"one.txt""two.txt"

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

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

1
2
3
4
5
#!/bin/sh
# Dependency
function archive_files {
    zip files.zip"$@"
}

不是直接在依赖脚本中使用sourceing实现脚本,而是使用包装两个"组件"的injector.sh容器:

1
2
3
4
#!/bin/sh
# Injector
source ./archive_files_zip.sh
source ./dependent.sh

archive_files依赖项刚刚被注入依赖脚本。

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

示例:删除DI

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

1
2
3
4
5
6
7
8
#!/bin/sh
# Dependent

# dependency look-up
source ./archive_files_zip.sh

touch        "one.txt""two.txt"
archive_files"one.txt""two.txt"

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

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

最后的话

DI并不像Java框架那样在很大程度上被强调和推广。

但它是一种通用的方法,用于分割以下方面的关注:

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

仅对依赖项查找使用配置没有帮助,因为每个依赖项(例如,新身份验证类型)的配置参数数量可能会更改,并且支持的依赖项类型(例如,新数据库类型)的数量也可能会更改。


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

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

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

Reel-to-reel portable tape recorder, mid-20th century.

上图是20世纪中叶的一幅卷对卷便携式磁带录音机的图像。来源。

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

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

  • 我们可以把卷轴放进机器里
  • 我们可以为卷筒提供一个钩子,把它放在哪里。
  • 如果我们使用第一个,我们需要打开机器来更换卷筒。如果我们选择第二个,也就是说放一个钩子到卷轴,我们就可以通过改变卷轴来播放任何音乐。同时也减少了仅播放卷轴中的任何内容的功能。

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

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

    • 高粘聚力和松耦合。
    • 外部化依赖,只关注责任。
    • 使事物成为组件,并结合起来形成一个具有高性能的大系统。
    • 它有助于开发高质量的组件,因为它们是独立开发的,并且经过了适当的测试。
    • 如果一个组件出现故障,用另一个组件替换它会有所帮助。

    现在有一天,这些概念构成了编程界众所周知的框架的基础。Spring Angular等是基于这个概念构建的著名软件框架。

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

    依赖注入示例

    以前我们写这样的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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();
    }

    通过依赖注入,依赖注入器将为我们除去实例化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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)是指将相互依赖的对象解耦。假设对象A依赖于对象B,所以我们的想法是将这些对象彼此分离。我们不需要使用新关键字硬编码对象,而需要在运行时共享对象的依赖项,尽管编译时是这样。如果我们谈论

    依赖注入如何在春季工作:

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

    控制反转(IOC)

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

    依赖注入的两种类型:

  • 构造器注入
  • Setter注入
  • 1。基于构造函数的依赖项注入:

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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>

    2。基于setter的依赖注入:

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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作为必需的依赖项。


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

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

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

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

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


    例如,我们有2个类ClientServiceClient将使用Service

    1
    2
    3
    4
    5
    public class Service {
        public void doSomeThingInService() {
            // ...
        }
    }

    无依赖注入

    方式1)

    1
    2
    3
    4
    5
    6
    public class Client {
        public void doSomeThingInClient() {
            Service service = new Service();
            service.doSomeThingInService();
        }
    }

    方式2)

    1
    2
    3
    4
    5
    6
    public class Client {
        Service service = new Service();
        public void doSomeThingInClient() {
            service.doSomeThingInService();
        }
    }

    方式3)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Client {
        Service service;
        public Client() {
            service = new Service();
        }
        public void doSomeThingInClient() {
            service.doSomeThingInService();
        }
    }

    1)2)3)使用

    1
    2
    Client client = new Client();
    client.doSomeThingInService();

    优势

    • 简单的

    缺点

    • Client级难测
    • 当我们更改Service构造函数时,我们需要在所有地方更改代码,创建Service对象。

    使用依赖项注入

    方法1)构造注入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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();
        }
    }

    使用

    1
    2
    3
    Client client = new Client(new Service());
    // Client client = new Client(new Service(), new SqliteDatabase());
    client.doSomeThingInClient();

    方式2)设定器注入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Client {
        Service service;

        public void setService(Service service) {
            this.service = service;
        }

        public void doSomeThingInClient() {
            service.doSomeThingInService();
        }
    }

    使用

    1
    2
    3
    Client client = new Client();
    client.setService(new Service());
    client.doSomeThingInClient();

    方式3)界面注入

    检查https://en.wikipedia.org/wiki/dependency_注入

    = =

    现在,这个代码已经遵循了Dependency Injection,而且测试Client类更容易。但是,我们仍然多次使用new Service(),当更改Service构造函数时,效果并不好。为了防止这种情况,我们可以使用类似DI注入器的1)简易手动Injector

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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(...));
        }
    }

    使用

    1
    Service service = Injector.provideService();

    2)使用库:用于Android匕首2

    优势

    • 使测试更容易
    • 当您更改Service时,只需要在injector类中更改它。
    • 如果您使用Constructor Injection,当您查看Client的构造函数时,您将看到Client类有多少依赖项

    缺点

    • 如果使用Constructor InjectionService对象是在Client创建时创建的,有时我们在Client类中使用函数而不使用Service创建的Service是浪费的。

    依赖注入定义

    https://en.wikipedia.org/wiki/dependency_注入

    A dependency is an object that can be used (Service)
    An injection is the passing of a dependency (Service) to a dependent object (Client) that would use it


    这意味着对象应该只有尽可能多的依赖项来完成它们的工作,依赖项应该很少。此外,在可能的情况下,对象的依赖关系应该是接口,而不是"具体"对象。(具体对象是用关键字new创建的任何对象。)松耦合提高了可重用性、易维护性,并允许您轻松地提供"模拟"对象来代替昂贵的服务。

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

    实现DI有两种主要方法:

  • 构造注入
  • 设值注入
  • 构造注入

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

    注意,构造函数接受一个接口,而不是具体的对象。另外,请注意,如果orderDAO参数为空,则会引发异常。这强调了接收有效依赖项的重要性。在我看来,构造函数注入是为对象提供依赖性的首选机制。在调用对象时,开发人员很清楚哪些依赖项需要提供给"person"对象才能正确执行。

    Setter注入

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

    setter注入不强制将依赖项传递给构造函数。相反,依赖项被设置到由需要的对象公开的公共属性上。正如前面所暗示的,这样做的主要动机包括:

  • 支持依赖项注入,而不必修改遗留类的构造函数。
  • 允许尽可能晚地并且仅在需要时创建昂贵的资源或服务。
  • 下面是上述代码的示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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;


    我想既然每个人都为DI写过信,那就让我问几个问题吧。

  • 当您有一个DI配置时,所有实际的实现(而不是接口)都将被注入到一个类中(例如服务到控制器),为什么这不是某种硬编码?
  • 如果我想在运行时更改对象呢?例如,当我实例化mycontroller时,我的配置已经说,将filelogger作为ilogger注入。但我可能想注入数据库记录器。
  • 每当我想更改我的aclass需要的对象时,我现在需要查看两个地方——类本身和配置文件。这是如何让生活更容易的?
  • 如果没有注入适当的aclass,那么模仿它更难吗?
  • 回到第一个问题。如果使用new object()是错误的,那么我们为什么要注入实现而不是接口呢?我想你们很多人都在说我们实际上是在注入接口,但是配置让你指定接口的实现,而不是在运行时。它是在编译期间硬编码的。
  • 这是基于@adam n发布的答案。

    为什么PersonService不再需要担心GroupMembershipService?您刚才提到GroupMembership依赖于多个对象(对象/属性)。如果pservice中需要gmservice,那么它将作为属性。不管你是否注射,你都可以模仿它。我唯一希望注入的时间是gmservice是否有更具体的子类,直到运行时才知道。然后您需要注入子类。或者如果你想把它作为单体或者原型。老实说,配置文件已经硬编码了所有内容,包括在编译期间要注入的类型(接口)的子类。

    编辑

    Jose Maria Arranz对DI的一个很好的评论

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

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

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

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

    关于可替换:使用非常简单的.properties archive和class.forname可以定义wich类可以更改。如果任何代码类都可以更改,Java就不适合你,使用脚本语言。顺便说一下:不重新编译就不能更改注释。

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


    依赖注入是指代码的一部分(例如类)以模块化的方式访问依赖项(代码的其他部分,例如它依赖的其他类)的方式(实际上是任何方式),而无需对它们进行硬编码(以便它们可以自由更改或重写,甚至可以根据需要在其他时间加载)。

    (而且,是的,它已经成为一个过于夸张的25美元的名字,一个相当简单的概念),我的.25美分


    我知道已经有很多答案了,但我发现这非常有用:http://tutorials.jenkov.com/dependency-injection/index.html

    无依赖性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class MyDao {

      protected DataSource dataSource =
        new DataSourceImpl("driver","url","user","password");

      //data access methods...
      public Person readPerson(int primaryKey) {...}

    }

    附属国:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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实例的任何类提供。


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

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

    我们只是将参数传递给构造函数。自从建设者被发明以来,我们就一直在这样做。

    "依赖注入"被认为是"控制反转"的一种类型,这意味着从调用者中取出了一些逻辑。当调用者传入参数时,情况并非如此,因此如果是DI,那么DI并不意味着控制反转。

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

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

    在做某事之前

    1
    gcc foo.o bar.o -o bar

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

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

    通常中间层为构造的对象提供工厂,它必须提供每个请求的对象类型必须满足的角色。这是因为有了一个隐藏构造细节的中间层,您已经招致了工厂强加的抽象惩罚,所以您也可以使用工厂。


    依赖注入是一种可能的解决方案,通常可以称为"依赖模糊"需求。依赖项模糊化是一种将"明显的"性质从向需要它的类提供依赖性的过程中去掉的方法,因此在某种程度上模糊了对所述类提供所述依赖性的过程。这不一定是坏事。实际上,通过模糊向类提供依赖项的方式,类外部的某个东西负责创建依赖项,这意味着在各种情况下,可以向类提供依赖项的不同实现,而不必对类进行任何更改。这对于在生产模式和测试模式之间切换(例如,使用"模拟"服务依赖项)非常有用。

    不幸的是,坏的部分是,有些人认为您需要一个专门的框架来进行依赖关系模糊处理,并且如果您选择不使用特定的框架来进行依赖关系模糊处理,那么您在某种程度上就是一个"较小"的程序员。另一个非常令人不安的神话是,依赖注入是实现依赖模糊的唯一途径。这是显而易见的、历史性的,显然是100%错误的,但是您将很难说服一些人,因为您的依赖性模糊需求有依赖性注入的替代方案。

    多年来,程序员已经了解了依赖项模糊化的需求,许多替代解决方案在依赖项注入之前和之后都得到了发展。有工厂模式,但也有许多使用threadlocal的选项,其中不需要对特定实例进行注入-依赖项被有效地注入到线程中,这有助于使对象(通过方便的静态getter方法)可用于任何需要它的类,而无需向T中添加注释。他类需要它,并设置复杂的XML"粘合剂"来实现它。当持久性(jpa/jdo或其他)需要依赖性时,它允许您更容易实现"tranaparent持久性",并且域模型和业务模型类纯粹由pojos组成(即没有特定于框架/锁定在注释中)。


    从Java《开发者》:Java 7的关键技术和多语言编程

    DI is a particular form of IoC, whereby the process of finding your dependencies is
    outside the direct control of your currently executing code.


    依赖注入(DI)是设计模式中的一种,它使用OOP的基本特性——一个对象与另一个对象之间的关系。当继承继承继承一个对象以执行更复杂和特定的另一个对象时,关系或关联只使用属性从一个对象创建指向另一个对象的指针。DI的强大功能与OOP的其他特性(如接口和隐藏代码)结合在一起。假设我们在图书馆有一个客户(订阅者),为了简单起见,这个客户只能借一本书。

    书籍界面:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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();
    }

    接下来我们可以有很多种书;其中一种是小说:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    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);
    }
    }

    现在订户可以与图书关联:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    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容器来完成的,这对我们来说是有效的。我们只需在代码中创建上下文并调用bean的初始化。在那一刻,注射已经自动完成了。


    摘自2010年10月的apress.spring.persistence.with.hibernate.oct.2010

    The purpose of dependency injection is to decouple the work of
    resolving external software components from your application business
    logic.Without dependency injection, the details of how a component
    accesses required services can get muddled in with the component’s
    code. This not only increases the potential for errors, adds code
    bloat, and magnifies maintenance complexities; it couples components
    together more closely, making it difficult to modify dependencies when
    refactoring or testing.


    依赖注入是与Spring框架相关的概念的核心,在创建任何项目的框架时,Spring都可能发挥至关重要的作用,而依赖注入就是这样产生的。

    实际上,假设在Java中,您创建了两个不同的类,即类A和B类,以及在类A中使用的函数类中的任何可用函数,因此可以使用依赖注入。你可以用同样的方法将一个类的对象装入另一个类中,这样你就可以在另一个类中注入一个完整的类,使它可以被访问。这样就可以克服依赖性。

    依赖注入只是将两个类粘合在一起,同时使它们保持分离。


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

    DI是国际奥委会春季主席的执行,他说"不要打电话给我们,我们会打电话给你"。使用依赖注入程序员不需要使用new关键字创建对象。

    对象一旦加载到Spring容器中,然后在需要时重用它们,方法是使用getbean(string beanname)方法从Spring容器中获取这些对象。


    依赖注入(DI)是依赖反转原理(DIP)实践的一部分,也称为控制反转(IOC)。基本上,您需要做DIP,因为您希望使代码更模块化和单元可测试,而不仅仅是一个单片系统。因此,您开始识别代码中可以从类中分离出来并抽象出来的部分。现在抽象的实现需要从类外部注入。通常,这可以通过构造函数完成。因此,创建一个接受抽象作为参数的构造函数,这称为依赖注入(通过构造函数)。有关DIP、DI和IOC容器的更多说明,请阅读此处


    我将提出一个稍有不同、简短和精确的依赖注入定义,重点放在主要目标上,而不是技术手段上(从这里开始):

    Dependency Injection is the process of creating the static, stateless
    graph of service objects, where each service is parametrised by its
    dependencies.

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

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

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

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


    依赖注入是一种基于框架构建的"控制反转"原理的实现。

    GOF的"设计模式"中所述的框架是实现主控制流逻辑的类,这使得开发人员能够做到这一点,这样框架就实现了控制原理的反转。

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

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

    类实例是"依赖项",调用组件通过引用与类实例的外部绑定是"注入"。

    显然,您可以从OOP的角度以多种方式实现这种技术,例如,构造函数注入、设置器注入、接口注入。

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

    这样,在设计组件时,您可以只关注它们的体系结构和它们的特定逻辑,信任与其他对象协作的接口,而不必担心所使用的对象/服务的任何类型的实现更改,并且如果您使用的同一个对象将被完全替换(显然是考虑到面对)


    来自克里斯托弗·诺林,巴勃罗·迪尔曼的书《学习角度-第二版》:

    "随着我们的应用程序的增长和发展,我们的每一个代码实体都将在内部需要其他对象的实例,这在软件工程领域中被称为依赖关系。将这些依赖项传递给依赖客户端的操作称为注入,它还需要另一个名为注入器的代码实体的参与。注入器将负责实例化和引导所需的依赖项,以便它们从成功注入客户机的那一刻起就可以使用。这一点非常重要,因为客户机不知道如何实例化自己的依赖项,只知道它们为使用它们而实现的接口。"


    依赖注入是一种将分离的组件与它们的一些依赖项不可知的实践,这遵循一个可靠的准则,即

    Dependency inversion principle: one should"depend upon abstractions,
    not concretions.

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

    我推荐这篇关于复合词根的伟大文章http://blog.ploeh.dk/2011/07/28/compositionroot/作者:Mark Seemann

    本文的要点如下:

    A Composition Root is a (preferably) unique location in an application
    where modules are composed together.

    Only applications should have Composition Roots. Libraries and
    frameworks shouldn't.

    A DI Container should only be referenced from the Composition Root.
    All other modules should have no reference to the container.

    依赖注入框架DiNinja的文档是一个很好的例子,可以演示组合根和依赖注入的原理是如何工作的。https://github.com/di-ninja/di-ninja如我所知,是JavaScript中实现复合根设计模式的唯一一个dic。


    DI是真实对象之间实际交互的方式,没有一个对象对另一个对象的存在负责。应平等对待对象。它们都是物体。任何人都不应该表现得像造物主。这就是你如何公正对待你的目标。

    简单例子:

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


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

    有关更多详细信息,请参见此处输入链接说明