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

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


当前回答

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

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

马丁·福勒的一篇文章可能也很有用。

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

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

其他回答

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

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

在面向对象编程的世界中,DI与此类似。那里的值而不是常量文字是整个对象-但是将创建它们的代码从类代码中移出的原因是相似的-对象的更改比使用它们的代码更频繁。一个重要的情况是需要进行这样的改变,那就是测试。

我能想到的最好的类比是手术室中的外科医生和他的助手,在那里,外科医生是主要的人,他的助手在他需要时提供各种手术组件,以便外科医生能够专注于他最擅长的一件事(手术)。如果没有助手,外科医生每次需要时都必须自己取下部件。

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

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

何时使用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)

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

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

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

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

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

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

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

GoF的“设计模式”中所述的框架是实现主控制流逻辑的类,从而使开发人员能够这样做,这样框架实现了控制原则的反转。

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

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

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

显然,从OOP的角度来看,您可以以多种方式实现该技术,例如,构造函数注入、setter注入、接口注入。

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

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