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

时间:2017-10-12 12:05:02

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

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

我需要在PHP中理解它。

5 个答案:

答案 0 :(得分:23)

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

注射与反转

  • 依赖注入是一种 Inversion of Control 技术,用于提供对象('依赖关系')通过Dependency Injection Design Pattern来上课。通常通过以下方式之一传递依赖项:

    • 构造函数
    • 公共财产或领域
    • 公共制定者
  • 依赖反转原则(DIP)是一个软件设计指南,归结为关于de-coupling a class from its concrete dependencies的两条建议:

    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;
    }
}

在此示例中,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容器)

依赖性倒置

依赖性反转通常是通过阻止那些具有任何直接引用的类来解除具体类的分离。

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

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

class Foo {
    reader: SqlRecordReader;

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

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

在上面的示例中,类FooSqlRecordReader具有硬依赖性,但它唯一真正关心的是存在一个名为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抽象,强制Foo依赖IRecordReader代替SqlRecordReader满足DIP指南。

为什么DIP指南有用

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

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

在最后一个示例中,Foo 可能会收到SqlRecordReader,或者SoapRecordReader,或者FileRecordReader,或者甚至可能单位测试MockRecordReader - 关键在于它不知道或关心IRecordReader的不同可能实现 - 当然这些实现不仅仅是Liskov Substitution Principle

此外,它避免了潜在的肮脏场景,即急于开展工作的开发人员可能会考虑尝试" fudge"通过从基类SoapRecordReader继承FileRecordReaderSqlRecordReader来实现Liskov原则。

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

答案 1 :(得分:2)

请参阅此文here

作者用简单的词语区分这两者。 依赖注入==“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.

答案 2 :(得分:1)

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

依赖注入是对象提供其他对象依赖的能力。简而言之,这意味着其他事物依赖于其他事物。 Example Class A 使用Class B 的几个函数,现在Class A 需要创建Class B 的实例,这里使用DI 来使用。

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

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