依赖注入和依赖反转之间的区别

Ist*_*med 23 php design-patterns dependency-injection dependency-inversion

存在两种设计模式,即依赖注入和依赖性反转,文章在网上试图解释差异.但是,用更容易的话来解释它的必要性仍然存在.那里有人来接?

我需要在PHP中理解它.

Ben*_*ell 50

(注意:这个答案与语言无关,虽然问题特别提到PHP,但是对PHP不熟悉,我没有提供任何PHP示例).

注射与反转

  • 依赖注入是一种控制反转技术,用于通过依赖注入设计模式对象提供对象('依赖关系').通常通过以下之一传递依赖项:

    • 构造函数
    • 公共财产或领域
    • 公共二传手
  • 依赖倒置原则(DIP)是一个软件设计指南,归结为关于将类与其具体依赖关系解耦的两个建议:

    1. '高级模块不应该依赖于低级模块.两者都应该取决于抽象.
    2. '抽象不应该依赖于细节.细节应该取决于抽象.

依赖注入

首先,依赖注入与控制反转(IoC)不同. 具体来说,IoC是不同解决方案的保护伞,包括依赖注入,服务定位器,工厂模式,策略模式等.

考虑到依赖注入而设计的类可能如下所示:

// Dependency Injection Example...

class Foo {
    // Constructor uses DI to obtain the Meow and Woof dependencies
    constructor(fred: Meow, barney: Woof) {
        this.fred = fred;
        this.barney = barney;
    }
}
Run Code Online (Sandbox Code Playgroud)

在这个例子中,Meow并且Woof都是通过构造函数注入的依赖项Foo.

另一方面,没有依赖注入Foo设计的类可能只是创建和实例本身,或者可能使用某种服务定位器/工厂:MeowWoof

// Example without Dependency Injection...

class Foo {
    constructor() {
        // a 'Meow' instance is created within the Foo constructor
        this.fred = new Meow();

        // a service locator gets a 'WoofFactory' which in-turn
        // is responsible for creating a 'Woof' instance.
        // This demonstrates IoC but not Dependency Injection.
        var factory = TheServiceLocator.GetWoofFactory();
        this.barney = factory.CreateWoof();
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,依赖注入只是意味着一个类已经推迟了获取或提供自己的依赖关系的责任 ; 相反,责任在于任何想要创建实例的东西.(通常是IoC容器)


依赖倒置

依赖性反转通常是通过防止那些具有彼此直接引用的类来解除具体类的分离.

注意:依赖性反转通常在静态类型编程语言(如C#或Java)中更明确,因为这些语言对变量名执行严格的类型检查.另一方面,依赖性反转已经在动态语言(如Python或JavaScript)中被动地可用,因为这些语言中的变量没有任何特定的类型限制.

考虑使用静态类型语言的场景,其中类需要能够从应用程序的数据库中读取记录:

class Foo {
    reader: SqlRecordReader;

    constructor(sqlReader: SqlRecordReader) {
        this.reader = sqlReader;
    }

    doSomething() {
        var records = this.reader.readAll();
        // etc.
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,类Foo具有硬依赖性SqlRecordReader,但它唯一真正关心的是存在一个readAll()返回一些记录的方法.

考虑将SQL数据库查询分成单独的微服务的情况; 在Foo类将需要从删除服务读取记录来代替.或者,Foo单元测试需要从内存存储或平面文件中读取数据的情况.

如果顾名思义,SqlRecordReader包含数据库和SQL逻辑,那么任何迁移到微服务都需要Foo更改类.

依赖性反转指南建议SqlRecordReader应该用仅提供readAll()方法的抽象替换.即:

interface IRecordReader {
    Records[] getAll();
}

class Foo {
    reader: IRecordReader;

    constructor(reader: IRecordReader) {
        this.reader = reader;
    }
}
Run Code Online (Sandbox Code Playgroud)

根据DIP,它IRecordReader是一种抽象,并且强制Foo依赖IRecordReader而不是SqlRecordReader满足DIP指南.


为什么DIP指南很有用

关键字是指南 - 依赖性反转将间接添加到程序的设计中.添加任何类型的间接的明显缺点是复杂性(即人类理解正在发生的事情所需的认知"负载")增加.

在许多情况下,间接可以使代码更容易维护(修复错误,添加增强功能)但是:

在最后一个例子中,Foo 可能会收到一个SqlRecordReader,或者一个SoapRecordReader,或者一个FileRecordReader,或者甚至可能用于单元测试MockRecordReader- 关键是它不知道或不关心任何可能的实现IRecordReader- 当然这些实现是实现的在里氏替换原则.

此外,它避免了潜在的肮脏场景,即急于获得工作的开发人员可能会考虑通过继承SoapRecordReaderFileRecordReader从基类来"捏造"Liskov原则SqlRecordReader.

更糟糕的是,缺乏经验的开发人员甚至可能会更改SqlRecordReader自身,因此该类不仅具有SQL逻辑,还具有SOAP端点,文件系统以及可能需要的任何其他内容.(这种事情在现实世界中经常发生 - 特别是在维护不良的代码中,并且几乎总是代码嗅觉.)

  • @IstiaqueAhmed相关但不同:*依赖反转*是关于“细节取决于抽象”(即解耦,以便一个类不依赖于其他具体类),而*控制反转(IoC)*是关于能够使用某种通用框架来创建/连接对象并提供依赖项。IoC 还可以使用通用框架通过某种通用接口来实际控制对象(例如,一堆不同的类,每个类都实现自己的“Run()”方法,该方法可以由通用框架调用) (3认同)
  • 依赖倒置和控制反转 (IoC) 是一样的吗? (2认同)

AJA*_*AJA 15

在这里看到这篇文章

作者用简单的词来区分这两者。依赖注入==“给我它”和依赖倒置==“有人帮我处理这个,不知何故。” . 在依赖倒置原则中,高级模块是抽象的所有者。因此细节(抽象的实现)取决于抽象,因此取决于高级模块。依赖倒置!...依赖注入是不同的。抽象可能不由高级模块保留。因此,赋予更高级别对象的抽象可能不限于高级模块的需要。

依赖倒置:

您有一个更高级别的模块 X 和一个由 X 定义的抽象 Y。Z 实现 Y 并赋予 X。因此 Z 依赖于 X(通过 X 定义的抽象 Y)。

依赖注入:

您有一个更高级别的模块 X,它需要功能 A 和 B。Y 是一个抽象,其中包含功能 A、B 和 C。Z 实现 Y。由于 Z 实现 Y 并因此具有功能 A 和 B,Z 被赋予 X。现在 X 依赖于 Y。

  • 不过之前看过这个。你能在这里用更简单的词来概括吗? (2认同)

Kyl*_*rns 1

依赖注入是实现控制反转的一种方法(我假设您将其称为依赖反转),因此两者实际上并没有\xe2\x80\x99t 竞争,就像 DI 是 IoC 的专业化一样。其他常见的实现 IoC 的方法包括使用工厂或服务定位器模式。

\n

  • @IstiaqueAhmed 依赖倒置与依赖注入或控制倒置无关。依赖倒置是 SOLID 原则中的“D”。请参阅其他答案以获得更深入的见解,并谷歌搜索 SOLID (5认同)