已经有几个问题已经发布与具体问题 依赖注射, 如何时使用这和什么样的框架是有用的。但是,

什么是依赖注射和当为什么应该或不应该使用?

有帮助吗?

解决方案

依赖注射 是通过依赖其他 对象框架(依赖性注射器).

依赖注射使得测试更加容易。注射可以通过 构造.

SomeClass() 有其构造如下:

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

的问题:如果 myObject 涉及到复杂的任务,例如磁盘访问或访问网络,这是 做单元的测试 SomeClass().程序员都嘲笑 myObject 并且可能 拦截 工厂的呼吁。

替代解决方案:

  • 传递 myObject 在作为一个参数的构造
public SomeClass (MyClass myObject) {
    this.myObject = myObject;
}

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

  • 一个常见的替代定义 做什么构造.依赖注射可以通过制定者。(h/t@MikeVella).
  • 马丁*福勒 文件第三种选择(h/t@MarcDix), 课程明确地实现一个接口 对于依赖关系的程序员的愿望注入。

这是难以隔离的部件在单元的测试,而不依赖注射。

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

其他提示

到目前为止,我发现的最佳定义是 James Shore的一个

  

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

Martin Fowler的一篇文章也可能证明是有用的。

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

可以通过多种方式(例如构造函数注入或setter注入)将依赖项注入到对象中。甚至可以使用专门的依赖注入框架(例如Spring)来做到这一点,但它们当然不是必需的。您不需要这些框架具有依赖注入。显式地实例化和传递对象(依赖关系)与框架注入一样好。

我在松散耦合方面找到了这个有趣的例子:

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

例如,考虑 Car 对象。

Car 取决于车轮,发动机,燃油,电池等运行。传统上,我们定义此类依赖对象的品牌以及 Car 对象的定义。

没有依赖注入(DI):

class Car{
  private Wheel wh = new NepaliRubberWheel();
  private Battery bt = new ExcideBattery();

  //The rest
}

此处, Car 对象负责创建依赖对象。

如果我们想在初始 NepaliRubberWheel()之后更改其依赖对象的类型 - 比如 Wheel ,该怎么办? 我们需要使用新的依赖关系来重新创建Car对象,例如 ChineseRubberWheel(),但只有 Car 制造商才能这样做。

然后依赖注入为我们做了什么?

使用依赖项注入时,会在运行时为对象提供其依赖项,而不是编译时间(汽车制造时间)。 这样我们现在可以随时更改 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;
  }
}

来源: 了解依赖注入

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

例如,请考虑以下条款:

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

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

接受的答案很好 - 但我想补充一点,DI非常类似于代码中经典的避免硬编码常量。

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

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 = new Car();

这个问题与这样的代码,我们紧紧地联接以GasEngine如果我们决定要改变它ElectricityEngine然后我们将需要改写车类。和更大的应用程序的更多问题和头痛,我们将必须增加和使用新型的发动机。

换句话说用这种方法是,我们的高级汽车的类依赖于较低的水平GasEngine类违反依赖倒置的原则(浸渍)从固体。浸表明,我们应该取决于抽象,没有具体的课程。因此,要满足这个我们介绍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();
        }
    }

现在我们的汽车类依赖于仅IEngine口,而不是一个具体的执行情况的发动机。现在,唯一的诀窍是我们如何创造一个实例车,并给它一个实际的具体的动机类似GasEngine或ElectricityEngine.这是哪里 依赖注射 来.

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

在这里我们的基本注入(通过)我们的依赖(发动机的实例)来汽车的构造。所以现在我们的类之间的松散耦合对象及其依赖性,我们可以很容易添加新的类型的引擎,而不改变汽车类。

主要的利益 依赖注射 这类是较为松散耦合的,因为他们没有硬编码的依赖关系。这下的依赖倒置的原则,这是上面提到的。而不是参照具体实现方案,课程要求的抽象(通常 接口)是提供给他们上课的时候是构成。

因此,在结束 依赖注射 只是一个技术 实现之间的松散耦合对象及其依赖性。而不是直接依赖关系的实例,这类需求在 为了执行其行动,依赖提供给类 (最常见)通过构造的注射。

此外,当我们有多依赖关系,它是非常好的做法,使用反转的控制(IoC)容器,我们可以告诉哪些接口应该是映射到其具体实现我们所有依赖性和我们可以把它解决那些依赖于我们的时候,它构造我们的对象。例如,我们可以指定在映射,为国际奥委会的容器, IEngine 依赖,应该进行映射的 GasEngine 类和当我们要求国际奥委会的容器的一个实例,我们的 类,它会自动建造我们的 GasEngine 依赖传递。

更新: 看课程有关的EF从核心茱莉勒曼最近也喜欢她简短的定义有关。

依赖注射是一种模式允许您应用程序来注射 物体的飞行课,需要他们,没有迫使那些 类以负责的那些对象。它可以让你的代码 更多的松散耦合、和实体框架的核心插在这个相同的 系统的服务。

让我们来想象一下,你想去钓鱼:

  • 而不依赖注射,你需要采取的一切照顾自己。你需要找到一艘船购买捕鱼杆,看起来为诱饵,等等。这是可能的,当然,但它提出了很多的责任。在软件方面,这意味着你必须执行查找所有这些事情。

  • 与依赖关系注入,其他人需要照顾的所有制和使得所需要的设备提供给你。你将接受("注")船上的鱼竿和鱼饵-都准备使用。

是最简单的解释 依赖注射依赖关系注入容器 我曾经看到:

而不依赖注射

  • 应用程序需求Foo(例如一个控制器),因此:
  • 应用程序创建Foo
  • 应用程序调Foo
    • Foo需要的条(例如一个服务),因此:
    • Foo创造了吧
    • Foo电话吧
      • 酒吧的需要姆(服务,一个储存库, ...),因此:
      • 酒吧创建姆
      • 酒吧做一些事情

与依赖注射

  • 应用程序需求Foo,这需求吧,这需要集,因此:
  • 应用程序创建姆
  • 应用程序创造条,并赋予它姆
  • 应用程序创建Foo,并赋予它吧
  • 应用程序调Foo
    • Foo电话吧
      • 酒吧做一些事情

使用依赖关系注入容器

  • 应用程序需求Foo这样:
  • 应用程序得到Foo从容器,所以:
    • 容器创建姆
    • 容器创造条,并赋予它姆
    • 容器创建Foo,并赋予它吧
  • 应用程序调Foo
    • Foo电话吧
      • 酒吧做一些事情

依赖注射依赖注射的容器 是的不同事情:

  • 依赖注射是一种用于编写更好的代码
  • 二容器是一种工具,以帮助注入依赖

你不需要一个容器做的依赖注射。但是一个容器可以帮助你。

不是“依赖注入”只是意味着使用参数化构造函数和公共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(); 
    ... 
  } 
}

什么是依赖注射(二)?

正如其他人所说的那样, 依赖注射(二) 删除责任的直接创造和管理的寿命、其他对象的实例在我们的类的利益(消费者类)是依赖(在 UML感).这些实例而不是通过我们的消费阶层,通常作为构造的参数或通过制定者的财产(管理的依赖对象的实例,并传递给消费者类通常是通过执行一个 反转的控制(IoC) 容器,但这是另一个主题).

二、浸和固体

具体而言,在范例的罗伯特*C*马丁 固体原则的目的导向的设计, DI 是的一个可能实现的 依赖倒置的原则(浸渍).的 浸是的 DSOLID -其他浸实现包括服务定位,并插件模式。

目的是浸到解紧张、混凝土之间的依赖关系课程,相反,放松耦合装置的抽象,从而可以实现通过一个 interface, abstract classpure virtual class, 根据语言和方法使用。

没有的浸染,我们的代码(我们称这个'消费阶层')直接联接到一个具体的依赖性,并往往也是负担与责任,知道如何获得和管理的一个实例,这种依赖关系,即从概念上说:

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

而后应用的浸,要求放松,并且关注获取和管理的使用寿命 Foo 依赖已经删除:

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

为什么使用浸(二)?

去偶之间的依赖关系课程以这种方式允许 很容易替换 这类依赖与其他实这也满足的先决条件的抽象(例如依赖可以交换另一实施的同样的接口)。而且,正如其他人已经提到,可能的话 最常见的原因分类,通过汲取的是允许消费阶层进行测试的隔离,因为这些同样的依赖性,现在可以存根和/或嘲笑。

一个后果的DI是,生命周期管理的对象的依赖关系的实例是不再控制的消耗类,作为对象的依赖关系,现在被传递到消费阶层(通过的构造或装定注射)。

这可以被视为以不同的方式:

  • 如果使用寿命控制的依赖性,通过消耗类的需要被保留,控制可以重新建立通过注入一种(摘要)的工厂,用于创建扶养类情况下,进入消费者类。消费者将能够获得实例通过 Create 在工厂的需要,以及处理这些情况下,一旦完成。
  • 或者,使用寿命控制的依赖性实例可以被放弃的一IoC容器(更多的有关这下文)。

当使用迪?

  • 那里有可能将需要替代一种依赖对等的实现,
  • 任何时间在那里你将需要单元的测试方法中的一个类隔离的依赖性,
  • 其中的不确定性的生命周期的依赖性可能需要的试验(例如嘿, MyDepClass 是thread safe-什么如果我们使一个单一实例并注入同一个实例入所有消费者?)

这里有一个简单的C#执行情况。鉴于以下的消费类:

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

虽然看似无害的,它有两个 static 依赖于其他两类, System.DateTimeSystem.Console, ,这不仅限制在记录输出的选择(记录到控制台将毫无价值,如果没有人看),但更糟糕的是,它难以自动地给出测试的依赖不确定的系统时钟。

我们可以但是申请 DIP 这类中,通过抽象的关切时间戳作为一个依赖,并联接 MyLogger 只有一个简单的接口:

public interface IClock
{
    DateTime Now { get; }
}

我们也可以放松的依赖 Console 以一个抽象的概念,例如一个 TextWriter.依赖注射通常是实现为 constructor 注入(通过一个抽象的依赖性作为一个参数的构造的一种消耗类)或 Setter Injection (通过依赖通过 setXyz() 二传或.净额财产 {set;} 定义)。Constructor注是首选,因为这保证了类将在一个正确的态后建设和允许的内部依赖性的领域是标明为 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 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容器构造的注射)

一个自动化的单元的测试可以建立,这明确证明,我们的记录是否正常工作,因为我们现在拥有控制权的依赖的时间,我们可以间谍的书面产出:

[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 实例")
  • 政策可以设置的使用寿命管理的各个依赖性,例如创建一个新的对象为每个消费者的实例,分享一个单独的依赖性实例在所有消费者分享同样的依赖性实例仅在相同的线,等等。
  • 中。网、海洋学委员会的容器都知道的协议,例如 IDisposable 并将承担责任的 Disposing 依赖关系与配置的使用寿命管理。

通常,一旦IoC容器已经配置/自举,他们无缝运行的背景,让的编码器把重点放在码在手,而不是担心的依赖关系。

钥匙给迪友好的代码就是要避免静态偶合的课程,而不使用新的()建立依赖关系

按上面的例子,脱钩的依赖性并要求一些设计上的努力,并为开发,还有一个模式转变的需要打破的习惯 new荷兰国际集团的依赖关系是直接的,而不是信任的容器管理的依赖性。

但好处是许多人,特别是在能力进行彻底的测试类的利益。

注意到 :建立/mapping/投(通过 new ..())的POCO/组/化的交互/实体图表/匿名JSON突et al-即"数据仅"类或记录的使用或返回的方法 被视为依赖关系(在UML意义上的),并没有受到DI。使用 new 为这些项目只是罚款。

使依赖注入概念易于理解。我们举一个开关按钮的例子来切换(打开/关闭)灯泡。

没有依赖注入

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

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

整点的依赖注射(二)是保持应用程序的源代码 干净的稳定:

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

实际上,每一个设计图案分隔的关切,以使未来变化的影响最小的文件。

具体领域的DI是国代表团的依赖配置和初始化。

例如:二shell script

如果你偶尔的工作以外的Java,回想一下如何 source 通常用在许多脚本语言(外壳、Tcl等, 甚至 import 在Python滥用于这一目的)。

考虑简单 dependent.sh 脚本:

#!/bin/sh
# Dependent
touch         "one.txt" "two.txt"
archive_files "one.txt" "two.txt"

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

你定义 archive_filesarchive_files_zip.sh 执行script(使用 zip 在这种情况下):

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

而不是的 source-ing执行直接的脚本中所依赖的一个,你使用 injector.sh "容器"包裹两个"组成部分":

#!/bin/sh 
# Injector
source ./archive_files_zip.sh
source ./dependent.sh

archive_files 依赖 刚刚被 注射依赖 脚本。

你可能已经注入的依赖它实现 archive_files 使用 tarxz.

例如:除迪

如果 dependent.sh 脚本用的依赖关系是直接的,该方法将被称为 依赖查找 (这是相对的 依赖注射):

#!/bin/sh
# Dependent

# dependency look-up
source ./archive_files_zip.sh

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

现在的问题是依赖"的组成部分"已经执行初始化本身。

的"组成"'s源代码不是 干净的 也不 稳定 因为每一个变化中的初始化的依赖关系需要新的释放"组分"'s源码文件。

最后一句话

二是不是因为很大程度上强调和推广,作为在Java框架。

但这是一个通用办法分割的关切:

  • 应用程序 发展 ( 源代码的生命周期的释放)
  • 应用程序 部署 ( 目标的环境中独立生命周期)

使用配置仅仅用 依赖查找 没有帮助,因为数量结构的参数可能变化的依赖(例如新类型的认证),以及数量的支持类型的依赖(例如新数据库的数据类型)。

上述所有的答案是好的,我的目的是解释这一概念在一个简单的方式使任何人都没有编程的知识也可以理解的概念

依赖注射是一个设计图案,帮助我们创建复杂的系统,在一个更简单的方式。

我们可以看到各种各样的应用的这种模式在我们的日常生活。一些例子磁带录像机、VCD光盘驱动器,等等。

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

上面的图像是卷轴到卷轴便携式磁带录像机,20世纪中叶。 来源.

主要目的磁带录像机机的录制或播放的声音。

同时设计一个系统需要一卷录制或播放的声音和音乐。有两种可能性,用于设计这个系统

  1. 我们可以把该卷的内部机器
  2. 我们可以提供一个钩,用卷轴里可以放置。

如果我们使用的第一个,我们需要打开机改变了卷轴。如果我们选择第二个,也就是把一个钩,用于卷,我们得到一个额外的好处的玩任何乐改变通盘。并且还减少功能,只要玩什么的卷轴。

喜欢聪明的依赖注射过程的外部化的依赖只注重特定功能的组成,使独立成分可以被联接起来,形成一个复杂的系统。

主要的好处,我们通过采用依赖注射。

  • 高凝聚力和松散耦合。
  • 外依赖和看只有上的责任。
  • 使事件和以结合起来形成较大的系统具有较高的的能力。
  • 它有助于制订高质量成分,因为他们都独立开发了它们正常进行测试。
  • 它有助于更换部件与另一个如果一个失败。

现在一天,这些概念的基础上形成的众所周知的框架在编程的世界。弹簧的角度等等,都是众所周知的软件的框架建立在这个概念

依赖注射是一种模式用于创建实例的对象,其他的对象依靠不知道在编制时间,这类会被用来提供这种功能或简单的方式注入的性质的对象是所谓的依赖注射。

例为依赖注射

以前我们正在编写这样的代码

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)意味着解耦彼此依赖的对象。假设对象A依赖于对象B,因此想法是将这些对象彼此分离。我们不需要使用new关键字对对象进行硬编码,而是在编译时尽管在运行时共享对象的依赖关系。 如果我们谈论

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

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

控制反转(IOC)

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

两种类型的依赖注入:

  1. 构造函数注入
  2. Setter Injection
  3. 1。基于构造函数的依赖注入:

    当容器调用具有多个参数的类构造函数时,就完成了基于构造函数的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>
    

    2。基于setter的依赖注入:

    基于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作为必需的依赖项。

我能想到的最好的比喻是手术室里的外科医生和他的助手,外科医生是主要人员,他的助手在需要时提供各种手术部件,以便外科医生可以集中精力关于他最擅长的一件事(手术)。没有助手,外科医生每次需要时都必须自己拿到组件。

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

DI使您更接近单一责任(SR)原则,例如外科医生可以专注于手术

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

原因:因为您希望您的代码易于测试,可模拟等,以便您可以快速测试您的更改并将其推向市场。除此之外,为什么你不会在你有更多控制权的代码库之旅中有很多很棒的免费工具/框架来支持你。

这意味着对象应该只具有执行其工作所需的依赖项,并且依赖项应该很少。此外,对象的依赖关系应该在接口上而不是在&#8220;具体的&#8221;对象,如果可能的话。 (具体对象是使用关键字new创建的任何对象。)松散耦合可提高可重用性,更易于维护,并允许您轻松提供&#8220;模拟&#8221;对象代替昂贵的服务。

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

实施DI有两种主要方法:

  1. 构造函数注入
  2. Setter injection
  3. 构造函数注入

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

    请注意,构造函数接受接口而不是具体对象。另请注意,如果orderDao参数为null,则抛出异常。这强调了接收有效依赖的重要性。在我看来,构造函数注入是赋予对象依赖性的首选机制。开发人员在调用对象时很清楚需要将依赖关系赋予&#8220; Person&#8221;正确执行的对象。

    Setter Injection

    但请考虑以下示例&#8230;假设您有一个具有十个没有依赖关系的方法的类,但是您要重新添加一个依赖于IDAO的新方法。您可以更改构造函数以使用构造函数注入,但这可能会强制您更改所有构造函数调用。或者,您可以添加一个新的构造函数来获取依赖项,但是开发人员如何轻松地知道何时使用一个构造函数而不是另一个构造函数。最后,如果创建依赖项非常昂贵,为什么它应该只在很少使用的情况下创建并传递给构造函数? &#8220; Setter Injection&#8221;是另一种可以在这种情况下使用的DI技术。

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

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

      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;
      

例如,我们有2类 ClientService. 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 构造,我们需要改变码在所有地方创建 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) 注射器

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)使用的图书馆:对于安卓 dagger2

优点

  • 使试验容易
  • 当你修改的 Service, 你只需要改变它在喷射器类
  • 如果你使用 Constructor Injection, 当你看的构造 Client, 你会看到许多依赖关系的 Client

缺点

  • 如果你使用 Constructor Injection, , Service 目的是创建时 Client 创建,有时我们用功能 Client 类没有用 Service 所以创建 Service 是浪费

依赖注射的定义

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

一个依赖性的一个目的是可以使用(Service)
注射的是通过一个依赖(Service)向依赖对象(Client),将使用它

我认为既然每个人都为DI写过,我就问几个问题。

  1. 当你有一个DI的配置,其中所有实际的实现(不是接口)将被注入一个类(例如对于控制器的服务)为什么不是某种硬编码?
  2. 如果我想在运行时更改对象怎么办?例如,我的配置已经说明当我实例化MyController时,将FileLogger注入ILogger。但我可能想注入DatabaseLogger。
  3. 每次我想更改我的AClass需要的对象时,我现在需要查看两个地方 - 类本身和配置文件。这怎么能让生活更轻松?
  4. 如果没有注入AClass的Aproperty,是否更难嘲笑它?
  5. 回到第一个问题。如果使用new object()很糟糕,我们为什么要注入实现而不是接口呢?我想你们很多人都在说我们实际上正在注入接口,但配置会让你指定该接口的实现。不在运行时..它在编译期间是硬编码的。
  6. 这是基于@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和注释也是源代码)。问题是这种锅炉板减少在非常非常简单的情况下是真实的(一个实例 - 每个类和类似),有时在现实世界中挑选适当的服务对象并不像将类映射到单个对象那么容易。

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

(和ps,是的,它变成了一个过于夸张的25美元名称,一个相当简单的概念),我的 .25 美分

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

流行的答案是无益的,因为它们以一种无用的方式定义依赖注入。让我们同意“依赖”。我们指的是我们的对象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”的人不需要知道酒吧取决于foo。依赖性被注入“make bar”之间。和gcc。

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

通常,中间级别为构造的对象提供工厂,这些工具必须提供每个请求的对象类型必须满足的角色。这是因为通过具有隐藏构造细节的中间级别,您已经承担了工厂施加的抽象惩罚,因此您不妨使用工厂。

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

不幸的是,有些人认为你需要一个专门的框架来进行依赖性混淆,并且如果你选择不使用特定的框架来做某个人,你就会成为一个“较小”的程序员。许多人认为另一个非常令人不安的神话是依赖注入是实现依赖性混淆的唯一方法。这显然是历史性的,显然是100%错误的,但是你会难以说服某些人为依赖性混淆要求提供依赖注入的替代方案。

程序员已经理解了多年来的依赖性混淆要求,并且在构思依赖注入之前和之后已经发展了许多替代解决方案。有工厂模式,但是使用ThreadLocal还有很多选项,不需要注入特定实例 - 依赖项有效地注入到线程中,这有利于使对象可用(通过方便的静态getter方法)到需要它的任何类,而不必向需要它的类添加注释,并设置复杂的XML“粘合”以实现它。当持久性需要依赖项(JPA / JDO或其他)时,它允许您更容易实现“转换持久性”,并且域模型和业务模型类完全由POJO组成(即没有特定于框架/锁定注释)。 / p>

从书中,'基于良好的Java开发人员:Java 7和多语言的重要技术编程

  

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

来自Book Apress.Spring.Persistence.with.Hibernate.Oct.2010

  

依赖注入的目的是分离工作   从您的应用程序业务中解析外部软件组件   logic.Without依赖注入,组件的细节   访问所需的服务可能会混淆组件&#8217; s   码。这增加了代码,这不仅增加了错误的可能性   臃肿,放大维护的复杂性;它耦合组件   更密切地结合在一起,使得很难修改依赖关系   重构或测试。

依赖注入(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的初始化。在那一刻,注射已经自动完成。

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

DI是Spring的IOC负责人的实施,其中写着“不要打电话给我们,我们会打电话给你”。使用依赖注入程序员不需要使用new关键字创建对象。

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

依赖注入是与Spring Framework相关的概念的核心。虽然创建任何项目的框架弹簧可能会发挥至关重要的作用,这里依赖注入来自投手。

实际上,假设在java中你创建了两个不同的类,如A类和B类,以及B类中你想要在A类中使用的任何函数,那么在那时可以使用依赖注入。 在其他地方你可以创建一个类的对象,就像你可以在另一个类中注入一个完整的类以使其可访问一样。 通过这种方式可以克服依赖。

依赖性注射只是简单地使两个类别同时保持不变。

依赖注入(DI)是依赖性倒置原则(DIP)实践的一部分,也称为控制反转(IoC)。基本上你需要做DIP,因为你想让你的代码更加模块化和单元测试,而不是只有一个单片系统。因此,您开始识别可以与类分离并抽象掉的代码部分。现在,需要从类外部注入抽象的实现。通常这可以通过构造函数完成。因此,您创建一个接受抽象作为参数的构造函数,这称为依赖注入(通过构造函数)。有关DIP,DI和IoC容器的更多说明,您可以阅读这里

我建议对依赖注入的内容略有不同,简短而精确的定义,重点关注主要目标,而不是技术手段(跟随这里):

  

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

我们在应用程序中创建的对象(无论我们使用Java,C#还是其他面向对象语言)通常属于以下两类之一:无状态,静态和全局服务对象&#8220; (模块),以及有状态,动态和本地的数据对象&#8221;。

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

一个要求是服务必须通过其依赖性进行参数化。这意味着什么取决于给定系统中采用的语言和方法。通常,这采用构造函数参数的形式,但使用setter也是一种选择。这也意味着服务的用户隐藏了服务的依赖关系(当调用服务方法时)。

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

依赖注入是“控制反转”的一种实现方式。基于框架构建的原则。

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

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

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

类实例是“ 依赖项 ”,调用组件与类实例的外部绑定通过引用它是“ < EM>注射 &QUOT;

显然,您可以从OOP的角度以多种方式实现此技术,例如参见构造函数注入 setter注入接口注入

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

通过这种方式,在设计组件时,您可以专注于其架构及其特定逻辑,信任与其他对象协作的接口,而无需担心所使用的对象/服务的任何类型的实现更改,如果相同的对象也是如此你正在使用将被完全取代(显然尊重界面)。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top