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

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


当前回答

例如,我们有两类客户机和服务。客户端将使用服务

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

优势

易于理解的

缺点

难以测试客户端类当我们更改Service构造函数时,我们需要在所有位置更改代码createService对象

使用依赖注入

方式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

===

现在,这段代码已经遵循了依赖注入,测试客户端类更容易。然而,我们仍然多次使用新的Service(),并且在更改Service构造函数时效果不佳。为了防止这种情况,我们可以使用DI注射器1) 简单手动喷油器

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) 使用库:适用于Android dagger2

优势

使测试更容易更改服务时,只需在Injector类中更改如果您使用使用构造函数注入,当您查看Client的构造函数时,您将看到Client类有多少依赖项

缺点

如果使用构造函数注入,则在创建客户端时创建服务对象,有时我们在客户端类中使用函数而不使用服务,因此创建的服务被浪费

依赖注入定义

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

依赖项是可以使用的对象(服务)注入是将依赖项(Service)传递给将使用它的依赖对象(Client)

其他回答

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

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

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

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

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

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

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

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

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

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

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

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

(顺便说一句,是的,对于一个相当简单的概念,它已经成为一个过度炒作的25美元的名字),我的25美分

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

清除依赖项初始化代码无论使用的依赖关系如何稳定

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

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

示例:带有shell脚本的DI

如果您偶尔在Java之外工作,请回想一下源代码在许多脚本语言(Shell、Tcl等,甚至在Python中被误用)中的使用情况。

考虑简单的dependent.sh脚本:

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

脚本是依赖的:它无法单独成功执行(未定义存档文件)。

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

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

您可以使用一个injector.sh“container”来包装这两个“components”,而不是直接在依赖脚本中源代码化实现脚本:

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

archive_files依赖项刚刚注入到依赖脚本中。

您可能已经注入了使用tar或xz实现archive_files的依赖项。

示例:删除DI

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

#!/bin/sh
# Dependent

# dependency look-up
source ./archive_files_zip.sh

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

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

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

最后一句话

DI并不像Java框架那样被广泛强调和普及。

但这是一种通用的方法,可以解决以下问题:

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

仅将配置与依赖项查找一起使用没有帮助,因为每个依赖项的配置参数数量(例如,新的身份验证类型)以及支持的依赖项类型数量(例如新的数据库类型)可能会发生变化。

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

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