存在两种设计模式,即依赖注入(inject)和依赖倒置,网络中的文章试图解释差异。但是仍然需要用更简单的语言来解释它。有没有人可以上来?
我需要在 PHP 中理解它。
最佳答案
(注意:这个答案是语言不可知的,虽然这个问题特别提到了 PHP,但我对 PHP 不熟悉,我没有提供任何 PHP 示例)。
术语 - 依赖和耦合
在面向对象编程的上下文中,依赖项是与类具有 直接 关系的任何其他对象类型。当一个类直接依赖于另一个对象类型时,它可以被描述为 耦合到该类型的 。
一般来说,一个类使用的任何类型在某种程度上都是一种依赖。一个类可以通过多种不同的方式依赖另一种类型,包括:
一个类与其依赖关系越强,耦合就越紧密;因此,当一个类直接依赖于另一个具体类时(例如继承创建对基类的直接依赖的情况,或者构造函数为其实例变量创建新对象的情况),对该直接依赖的任何 future 更改都会更多可能会以蝴蝶效果风格“荡漾”。
注入(inject)与反转之间的区别
或者,更简洁地说:
依赖注入(inject)和控制反转 (IoC)
依赖注入(inject)通过确保类从不负责创建或提供它们自己的依赖项(因此也不对这些依赖项的生命周期负责)来应用 IoC 原则。
然而, IoC 不是依赖注入(inject) - 事实上,IoC 作为一个原则与依赖或依赖注入(inject)本身没有特别的关系;依赖注入(inject)是一种基于 IoC 原理的设计模式。
IoC 出现在许多其他上下文中,包括那些与对象创建或依赖完全无关的上下文,例如通过中介或消息泵传递消息以触发事件处理程序。 IoC 的其他(不相关)示例包括:
(从原始答案更新为关于 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;
}
}
在这个例子中,Meow
和 Woof
都是通过 Foo
构造函数注入(inject)的依赖项。另一方面,一个没有依赖注入(inject)设计的
Foo
类可能会简单地创建 Meow
和 Woof
实例本身,或者可能使用某种服务定位器/工厂:// 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
继承 SoapRecordReader
或 FileRecordReader
来“捏造”Liskov 原则更糟糕的是,没有经验的开发人员甚至可能会更改
SqlRecordReader
本身,以便该类不仅具有用于 SQL 的逻辑,还具有用于 SOAP 端点、文件系统和其他任何可能需要的逻辑。 (这种事情在现实世界中经常发生 - 特别是在维护不良的代码中,并且几乎总是 de-coupling a class from its concrete dependencies 。)
关于php - 依赖注入(inject)和依赖倒置的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46709170/