php - 依赖注入(inject)和依赖倒置的区别

标签 php design-patterns dependency-injection dependency-inversion

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

我需要在 PHP 中理解它。

最佳答案

(注意:这个答案是语言不可知的,虽然这个问题特别提到了 PHP,但我对 PHP 不熟悉,我没有提供任何 PHP 示例)。
术语 - 依赖和耦合
在面向对象编程的上下文中,依赖项是与类具有 直接 关系的任何其他对象类型。当一个类直接依赖于另一个对象类型时,它可以被描述为 耦合到该类型的
一般来说,一个类使用的任何类型在某种程度上都是一种依赖。一个类可以通过多种不同的方式依赖另一种类型,包括:

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

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

    注入(inject)与反转之间的区别
  • 依赖 注入(inject) 是一种 Inversion of Control 技术,用于通过 1025 34138 向类提供对象(“依赖”)。1025通常通过以下方式之一传递依赖项:
  • 一个构造函数
  • 公共(public)属性或字段
  • 公共(public) setter

  • 依赖关系 反转 原则 (DIP) 是一个软件设计指南,可归结为两个关于 0x25181242133411 的建议
  • '高级模块不应依赖于低级模块。两者都应该依赖于抽象。
  • '抽象不应该依赖于细节。细节应该取决于抽象。


  • 或者,更简洁地说:
  • 依赖注入(inject)是一种填充类实例变量的实现技术。
  • 依赖倒置是一个通用的设计指南,它建议类应该只与高级抽象有直接关系。

  • 依赖注入(inject)和控制反转 (IoC)
    依赖注入(inject)通过确保类从不负责创建或提供它们自己的依赖项(因此也不对这些依赖项的生命周期负责)来应用 IoC 原则。
    然而, IoC 不是依赖注入(inject) - 事实上,IoC 作为一个原则与依赖或依赖注入(inject)本身没有特别的关系;依赖注入(inject)是一种基于 IoC 原理的设计模式。
    IoC 出现在许多其他上下文中,包括那些与对象创建或依赖完全无关的上下文,例如通过中介或消息泵传递消息以触发事件处理程序。 IoC 的其他(不相关)示例包括:
  • 使用事件处理函数/方法来处理鼠标/键盘输入事件的窗口应用程序。
  • 一个使用 Controller 操作处理 HTTP 请求的 MVC Web 应用程序。

  • (从原始答案更新为关于 IoC 的单独解释)

    依赖注入(inject)模式
    依赖注入(inject)是一种设计模式,它应用 IoC 原则来确保类绝对不参与或意识到其构造函数或实例变量使用的对象的创建或生命周期——关于对象创建和填充实例变量的“常见”关注点而是推迟到框架中。
    也就是说,一个类可以指定它的实例变量,但不做任何填充这些实例变量的工作(除了使用构造函数参数作为“传递”)
    考虑到依赖注入(inject)而设计的类可能如下所示:
    // 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 构造函数注入(inject)的依赖项。
    另一方面,一个没有依赖注入(inject)设计的 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();
        }
    }
    
    所以依赖注入(inject)只是意味着一个类推迟了获取或提供它自己的依赖的责任;相反,责任在于任何想要创建实例的人。 (通常是一个 IoC 容器)

    依赖倒置原则 (DIP)
    依赖倒置从广义上讲是通过防止那些具有任何 直接 引用的类来解耦具体类。
    DIP 主要关注确保一个类只依赖于更高级别的抽象。例如,接口(interface)存在于比具体类更高的抽象级别。
    DIP 与注入(inject)依赖无关,尽管依赖注入(inject)模式是许多技术中的一种,它可以帮助提供所需的间接级别,以避免依赖低级细节和与其他具体类的耦合。
    注意:依赖倒置在静态类型的编程语言(如 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.
        }
    }
    
    在上面的例子中,尽管使用了依赖注入(inject),类 Foo 仍然对 SqlRecordReader 有一个硬依赖,但它唯一真正关心的是存在一个名为 readAll() 的方法,它返回一些记录。
    考虑 SQL 数据库查询后来被重构为需要更改代码库的单独微服务的情况; Foo 类需要从远程服务读取记录。或者,Foo 单元测试需要从内存存储或平面文件读取数据的情况。
    如果顾名思义,SqlRecordReader 包含数据库和 SQL 逻辑,则任何向微服务的移动都需要更改 Foo 类。
    依赖倒置指南建议 SqlRecordReader 应替换为仅提供 readAll() 方法的更高级别的抽象。 IE:
    interface IRecordReader {
        Records[] getAll();
    }
    
    class Foo {
        reader: IRecordReader;
    
        constructor(reader: IRecordReader) {
            this.reader = reader;
        }
    }
    
    根据 DIP,IRecordReader 是比 SqlRecordReader, and forcing Foo to depend on IRecordReader 0x2518122231343141 DIPsatisfiesReader 的更高级别的抽象。

    为什么 DIP 指南有用
    关键字是指南 - 依赖倒置为您的程序设计增加了间接性。添加任何类型的间接性的明显缺点是复杂性(即人类理解正在发生的事情所需的认知“负载”)增加。
    在许多情况下,间接性可以使代码更易于维护(修复错误,添加增强功能)但是:
    在最后一个例子,instead of可能会收到Foo,或者一个SqlRecordReader,或者一个SoapRecordReader,或者甚至一个单元测试FileRecordReader - 的一点是,它不知道或了解的MockRecordReader不同的可能实现什么关怀 - 提供当然,这些实现符合 Dependency Injection Design Pattern
    此外,它避免了潜在的肮脏场景,即急于让某些东西工作的开发人员可能会考虑通过从基类 IRecordReader 继承 SoapRecordReaderFileRecordReader 来“捏造”Liskov 原则
    更糟糕的是,没有经验的开发人员甚至可能会更改 SqlRecordReader 本身,以便该类不仅具有用于 SQL 的逻辑,还具有用于 SOAP 端点、文件系统和其他任何可能需要的逻辑。 (这种事情在现实世界中经常发生 - 特别是在维护不良的代码中,并且几乎总是 de-coupling a class from its concrete dependencies 。)

    关于php - 依赖注入(inject)和依赖倒置的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46709170/

    相关文章:

    java - Spring 默认范围单例与否?

    c# - .NET Core/EF 6 - 依赖注入(inject)范围

    javascript - jquery .load 不适用于函数参数

    php - 如何将数据从 iPhone 应用程序中的 SQLite 数据库发送到 Web 服务?

    javascript - 我如何在同一html页面中的php中调用javascript?

    php - 工厂方法的建议

    dependency-injection - 如何在 loopback 4 中进行方法注入(inject)

    php - 在 xampp 等本地服务器上设置和测试 facebook 连接

    c++ - 不同的行为取决于数据

    python - 使用组合、策略模式和字典更好地实例化存储在字典中的类