繁体   English   中英

依赖注入和依赖倒置的区别

[英]Difference between dependency injection and dependency inversion

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

我需要在 PHP 中理解它。

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

术语 - 依赖和耦合

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

一般来说,一个类使用的任何类型在某种程度上都是一种依赖。 一个类可以通过多种不同的方式依赖另一种类型,包括:

  • 实例变量使用的对象类型
  • 构造函数参数使用的对象类型
  • Accessor/Mutator 方法使用的对象类型
  • 直接创建新对象的构造函数(有时还有方法)
  • 继承

一个类与其依赖关系越强,耦合就越紧密; 因此,当一个类直接依赖于另一个具体类时(例如继承创建对基类的直接依赖的情况,或者构造函数为其实例变量创建新对象的情况),对该直接依赖的任何未来更改都会更多可能会以蝴蝶效果风格“荡漾”。


注入与反转之间的区别

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

    • 一个构造函数
    • 公共财产或领域
    • 公共二传手
  • 依赖倒置原则 (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;
    }
}

在这个例子中, MeowWoof都是通过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();
    }
}

所以依赖注入只是意味着一个类推迟了获取或提供它自己的依赖的责任 相反,责任在于任何想要创建实例的人。 (通常是一个 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.
    }
}

在上面的例子中,尽管使用了依赖注入,类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;
    }
}

根据 DIP, IRecordReader是比SqlRecordReader, and forcing更高级别的抽象SqlRecordReader, and forcing Foo to depend on IRecordReader instead of SqlRecordReader` 满足 DIP 准则。


为什么 DIP 指南有用

关键字是指南- 依赖倒置为您的程序设计增加了间接性。 添加任何类型的间接引用的明显缺点是复杂性(即人类理解正在发生的事情所需的认知“负载”)增加。

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

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

此外,它避免了潜在的肮脏场景,在这种场景中,急于让某些东西工作的开发人员可能会考虑通过从基类SqlRecordReader继承SoapRecordReaderFileRecordReader来尝试“ SoapRecordReader ”Liskov 原则。

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

在这里看到这篇文章

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

依赖倒置:

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

依赖注入是实现控制反转的一种方法(我假设您将其称为依赖反转),因此两者之间的竞争并不像 DI 是 IoC 的专业化那样激烈。 其他常见的实现 IoC 的方法包括使用工厂或服务定位器模式。

依赖注入是对象提供其他对象依赖的能力。 简而言之,这意味着其他事物依赖于其他事物。 示例 A 类现在使用 B 类的一些功能,A 类需要创建 B 类的实例,这里使用 DI 来使用。

IOC是为了颠倒不同的职责,例如您需要在家工作但您需要做饭才能吃现在列出的在家做饭您可以在线订购并且可以在您家门口使用,这意味着您可以专注于您的工作。 在这里,您将烹饪责任倒转为在线恢复。

依赖倒置原则 (DIP) 指出高级模块不应依赖于低级模块; 两者都应该依赖于抽象。 抽象不应该依赖于细节。 细节应该取决于抽象。

在这里你可以找到一个漂亮的PHP示例。
https://code.tutsplus.com/tutorials/dependency-injection-in-php--net-28146

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM