依赖关系注入和依赖关系反转的区别

存在两种设计模式,即依赖注入和依赖倒置,文章中有试图解释差异。但是,用更简单的语言解释它的必要性仍然存在。有人在那里上来吗?

我需要在PHP中理解它。


答案 1

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

术语 - 依赖关系和耦合

在面向对象编程的上下文中,依赖项是类与之有直接关系的任何其他对象类型。当一个类直接依赖于另一个对象类型时,可以将其描述为与该类型耦合

通常,类使用的任何类型在某种程度上都是依赖项。一个类有许多不同的方式依赖于另一种类型,包括:

  • 实例变量使用的对象类型
  • 构造函数参数使用的对象类型
  • 访问器/赋值器方法使用的对象类型
  • 直接创建新对象的构造函数(有时是方法)
  • 遗产

类与其依赖关系之间的关系越强,耦合越紧密;因此,当一个类直接依赖于另一个具体类时(例如,继承创建对基类的直接依赖关系的情况,或者构造函数为其实例变量创建新对象的情况),将来对该直接依赖关系的任何更改都更有可能以蝴蝶效应样式“涟漪”过来。


注射与反转的区别

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

    • 构造函数
    • 公共财产或字段
    • 公共设置者
  • 依赖关系反转原则(DIP)是一个软件设计指南,它归结为关于将类从其具体依赖关系中解耦的两个建议:

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

或者,更简洁地说:

  • 依赖关系注入是一种用于填充类的实例变量的实现技术。
  • 依赖关系反转是一个通用的设计准则,它建议类只应与高级抽象有直接关系。

依赖关系注入和控制反转 (IoC)

依赖关系注入通过确保类从不负责创建或提供自己的依赖项来应用 IoC 原则(因此也不负责这些依赖项的生存期)。

然而,IoC不是依赖注入 - 事实上,IoC作为一个原则与依赖关系或依赖注入本身没有特别的关系;依赖注入是一种基于 IoC 原理的设计模式。

IoC 在许多其他上下文中都可以看到,包括那些与对象创建或依赖项完全无关的上下文,例如通过中介器或消息泵传递消息以触发事件处理程序。IoC 的其他(不相关)示例包括:

  • 使用事件处理程序函数/方法处理鼠标/键盘输入事件的窗口应用程序。
  • 使用控制器操作处理 HTTP 请求的 MVC Web 应用程序。

(从原始答案更新为关于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;
    }
}

在此示例中,并且都是通过构造函数注入的依赖项。MeowWoofFoo

另一方面,在没有依赖注入的情况下设计的类可以简单地创建和实例本身,或者使用某种服务定位器/工厂:FooMeowWoof

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

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


依赖关系反转原则 (DIP)

依赖关系反转大致上是关于通过防止这些类彼此有任何直接引用来解耦具体类。

DIP 主要关注确保类仅依赖于更高级别的抽象。例如,接口存在于比具体类更高的抽象级别。

DIP 不是关于注入依赖关系的,尽管依赖关系注入模式是许多技术之一,它可以帮助提供所需的间接寻址级别,以避免依赖于低级细节和与其他具体类的耦合。

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

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

// class Foo depends upon a concrete class called SqlRecordReader.

class Foo {
    reader: SqlRecordReader;

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

    doSomething() {
        var records = this.reader.readAll();
        // etc.
    }
}

在上面的例子中,尽管使用了依赖注入,类仍然对 有硬依赖关系,但它唯一真正关心的是存在一个调用的方法,它返回一些记录。FooSqlRecordReaderreadAll()

考虑这样一种情况,即SQL数据库查询后来被重构为需要更改代码库的单独微服务;该类需要改为从远程服务读取记录。或者,单元测试需要从内存中存储或平面文件中读取数据的情况。FooFoo

顾名思义,如果包含数据库和 SQL 逻辑,则任何向微服务的移动都需要更改该类。SqlRecordReaderFoo

依赖关系反转指南建议,应将其替换为仅提供该方法的更高级别抽象。即:SqlRecordReaderreadAll()

interface IRecordReader {
    Records[] getAll();
}

class Foo {
    reader: IRecordReader;

    constructor(reader: IRecordReader) {
        this.reader = reader;
    }
}

根据DIP,它是一个比FooIRecordReaderSqlRecordReader'满足DIP指南的更高级的抽象。IRecordReaderSqlRecordReader, and forcing to depend oninstead of


为什么 DIP 指南有用

关键字是指南 - 依赖关系反转为程序设计增加了间接性。添加任何一种间接寻址的明显缺点是复杂性(即人类理解正在发生的事情所需的认知“负荷”)增加。

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

在最后一个例子中,可能会收到一个,或者可能是一个,或者可能是一个,或者甚至对于单元测试来说,它一个 - 重点是它不知道或不关心任何关于不同的可能实现 - 当然,前提是这些实现符合Liskov替换原则FooSqlRecordReaderSoapRecordReaderFileRecordReaderMockRecordReaderIRecordReader

此外,它避免了潜在的肮脏情况,即急于让某些东西工作的开发人员可能会考虑尝试通过继承或从基类继承或从基类“捏造”Liskov原则。SoapRecordReaderFileRecordReaderSqlRecordReader

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


答案 2

在此处查看此文章

作者用简单的语言区分了这两者。依赖注入==“Gimme it”和依赖倒置==“有人以某种方式为我照顾这个。在依赖关系反转原则中,高级模块是抽象的所有者。因此,细节(抽象的实现)取决于抽象,因此取决于高级模块。依赖关系颠倒!..依赖注入是不同的。抽象可能不由高级模块保留。因此,赋予更高级别对象的抽象可能不限于高级模块的需求。

依赖关系反转 :

你有一个更高级别的模块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。


推荐