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

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


当前回答

摘自《扎实的Java开发人员:Java 7和多语言编程的关键技术》一书

DI是IoC的一种特殊形式,因此查找依赖项的过程是不受当前执行代码的直接控制。

其他回答

我们可以实现依赖注入来了解它:

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

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

无依赖注入

应用程序需要Foo(例如控制器),因此:应用程序创建Foo应用程序调用FooFoo需要Bar(例如服务),因此:Foo创建BarFoo调用Bar酒吧需要Bim(服务、存储库,…),因此:条形图创建Bim酒吧有点事

使用依赖注入

应用程序需要Foo,需要Bar,需要Bim,因此:应用程序创建Bim应用程序创建Bar并赋予它Bim应用程序创建Foo并给它Bar应用程序调用FooFoo调用Bar酒吧有点事

使用依赖注入容器

应用程序需要Foo,因此:应用程序从容器中获取Foo,因此:容器创建Bim容器创建Bar并赋予它Bim容器创建Foo并给它Bar应用程序调用FooFoo调用Bar酒吧有点事

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

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

您不需要容器来执行依赖注入。然而,容器可以帮助您。

流行的答案毫无用处,因为它们以一种无用的方式定义依赖注入。让我们同意,“依赖性”是指我们的对象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

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

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

通常,中间层为构造的对象提供工厂,这些对象必须提供每个请求的对象类型都必须满足的角色。这是因为通过拥有一个隐藏构建细节的中间层,您已经受到了工厂施加的抽象惩罚,所以您不妨使用工厂。

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

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

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

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

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

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

我们可以把卷轴放在机器里我们可以为卷轴提供一个可以放置的钩子。

如果我们使用第一个,我们需要打开机器来更换卷轴。如果我们选择第二种方式,即为卷轴设置挂钩,那么通过改变卷轴,我们可以获得播放任何音乐的额外好处。并且还将功能减少到只播放卷轴中的任何内容。

类似地,依赖注入是将依赖性外部化以仅关注组件的特定功能的过程,以便独立组件可以耦合在一起形成一个复杂的系统。

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

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

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

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

依赖注入示例

以前我们编写的代码是这样的

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

你也可以阅读

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

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

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

SomeClass()的构造函数如下:

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

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

替代解决方案:

将myObject作为参数传入构造函数

public SomeClass (MyClass myObject) {
    this.myObject = myObject;
}

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

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

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

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