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

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


当前回答

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

简单示例:

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

其他回答

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

没有依赖注入,你需要自己处理所有的事情。你需要找一艘船,买一根鱼竿,寻找诱饵等等。当然,这是可能的,但这给你带来了很多责任。在软件方面,这意味着你必须对所有这些东西进行查找。通过依赖注入,其他人负责所有准备工作,并为您提供所需的设备。你将收到(“被注射”)船、鱼竿和鱼饵——所有这些都准备好使用。

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

来源:了解依赖注入

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

例如,考虑Car对象。

汽车依靠轮子、发动机、燃料、电池等运转。传统上,我们定义此类依赖对象的品牌以及Car对象的定义。

无依赖注入(DI):

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

  //The rest
}

在这里,Car对象负责创建从属对象。

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

那么依赖注入为我们做了什么。。。?

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

使用依赖注入后:

这里,我们在运行时注入依赖项(Wheel和Battery)。因此有了这个词:依赖注入。我们通常依赖于Spring、Guice、Weld等DI框架来创建依赖关系并在需要时注入。

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;
  }
}

其优点是:

分离对象的创建(换句话说,将使用与对象的创建分开)能够替换依赖项(例如:车轮、电池),而不改变使用它的类(汽车)促进“代码到接口而不是实现”原则在测试期间创建和使用模拟依赖关系的能力(如果我们想在测试期间使用模拟轮而不是真实实例,我们可以创建模拟轮对象并让DI框架注入Car)

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

public class Car
{
    public Car()
    {
        GasEngine engine = new GasEngine();
        engine.Start();
    }
}

public class GasEngine
{
    public void Start()
    {
        Console.WriteLine("I use gas as my fuel!");
    }
}

为了实例化Car类,我们将使用以下代码:

Car car = new Car();

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

换句话说,这种方法是我们的高级Car类依赖于低级GasEngine类,这违反了SOLID的依赖反转原理(DIP)。DIP建议我们应该依赖抽象,而不是具体的类。因此,为了满足这一点,我们引入了IEngine接口并重写如下代码:

    public interface IEngine
    {
        void Start();
    }

    public class GasEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I use gas as my fuel!");
        }
    }

    public class ElectricityEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I am electrocar");
        }
    }

    public class Car
    {
        private readonly IEngine _engine;
        public Car(IEngine engine)
        {
            _engine = engine;
        }

        public void Run()
        {
            _engine.Start();
        }
    }

现在我们的Car类只依赖于IEngine接口,而不是引擎的特定实现。现在,唯一的诀窍是我们如何创建Car的实例,并给它一个实际的具体Engine类,比如GasEngine或ElectricityEngine。这就是依赖注入的作用。

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

在这里,我们基本上将依赖项(Engine实例)注入(传递)到Car构造函数。因此,现在我们的类在对象及其依赖关系之间具有松散的耦合,我们可以在不更改Car类的情况下轻松添加新类型的引擎。

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

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

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

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

依赖注入是一种允许应用程序注入的模式对象,而无需强制类来负责这些对象。它允许您的代码更松散的耦合,并且实体框架核心插入到该框架中服务体系。

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

不幸的是,糟糕的部分是,有些人认为你需要一个专门的框架来进行依赖性混淆,如果你选择不使用特定的框架来做,那么你在某种程度上就是一个“低级”程序员。另一个非常令人不安的神话是,依赖性注入是实现依赖性混淆的唯一方法。这显然是历史性的,显然是100%错误的,但你很难说服一些人,依赖项注入可以替代依赖项混淆需求。

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

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

无依赖注入

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

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

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

使用依赖注入

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

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

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

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

public class SwitchTest { 
  TestToggleBulb() { 
    MockBulb mockbulb = new MockBulb(); 

    // MockBulb is a subclass of Bulb, so we can 
    // "inject" it here: 
    Switch switch = new Switch(mockBulb); 

    switch.ToggleBulb(); 
    mockBulb.AssertToggleWasCalled(); 
  } 
}

public class Switch { 
  private Bulb myBulb; 

  public Switch() { 
    myBulb = new Bulb(); 
  } 

  public Switch(Bulb useThisBulbInstead) { 
    myBulb = useThisBulbInstead; 
  } 

  public void ToggleBulb() { 
    ... 
    myBulb.Toggle(); 
    ... 
  } 
}`