有两种设计模式,即“依赖注入”和“依赖倒置”。网络上已经有文章试图解释它们之间的区别。但还需要用更简单的话来解释。有没有人可以帮忙呢?
我需要用 PHP 来理解它。
有两种设计模式,即“依赖注入”和“依赖倒置”。网络上已经有文章试图解释它们之间的区别。但还需要用更简单的话来解释。有没有人可以帮忙呢?
我需要用 PHP 来理解它。
依赖注入通过确保类不负责创建或提供自己的依赖项(因此也不负责这些依赖项的生命周期),来应用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;
}
}
Meow
和Woof
都是通过Foo
构造函数进行注入的依赖项。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();
}
}
依赖倒置主要是通过防止具体类之间有直接引用来解耦。
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()
的方法,该方法返回一些记录。Foo
类需要从远程服务中读取记录。或者,当Foo
单元测试需要从内存存储或平面文件中读取数据时的情况。SqlRecordReader
包含数据库和SQL逻辑,那么任何转向微服务的举措都需要更改Foo
类。SqlRecordReader
,该抽象仅提供readAll()
方法。即:interface IRecordReader {
Records[] getAll();
}
class Foo {
reader: IRecordReader;
constructor(reader: IRecordReader) {
this.reader = reader;
}
}
IRecordReader
比SqlRecordReader
是一个更高级的抽象。将Foo
从依赖于SqlRecordReader
改为依赖于IRecordReader
可以满足DIP准则。
Run()
方法,该方法可以被通用框架调用)。 - Ben CottrellIRecordReader
已经是一个抽象类,因此解决了直接依赖于实现细节的问题,为什么仍然建议从外部传递 IRecordReader
?那么使用 Foo
的类不就依赖于 IRecordReader
了吗?感觉 IRecordReader
的依赖只是被移动了。 - Linus请查看此文 这里
作者用简单的语言区分了这两个概念。依赖注入 == “给我它”,而依赖倒置 == “有人以某种方式替我处理这个”。在依赖倒置原则中,高层模块是抽象的所有者。因此,细节(抽象的实现)取决于抽象,因此也取决于高级模块。依赖反转!.. 依赖注入不同。抽象可能不由高级模块保留。因此,提供给更高级对象的抽象可能不仅限于高级模块的需求。
依赖倒置:
您有一个高层模块 X 和一个由 X 定义的抽象 Y。Z 实现了 Y,并被赋予 X。因此,Z 依赖于 X(通过 X 定义的抽象 Y)。
依赖注入:
您有一个需要功能 A 和 B 的高层模块 X。Y 是包含功能 A、B 和 C 的抽象。Z 实现了 Y。由于 Z 实现了 Y,因此具有功能 A 和 B,将 Z 给予 X。现在 X 依赖于 Y。
依赖注入是实现控制反转的一种方式(我假设您所指的依赖反转是指控制反转),因此两者并不是在竞争,而 DI 是 IoC 的一种专业化。其他常见的实现 IoC 的方法包括使用工厂或服务定位器模式。
public interface ILog
{
void WriteLog(string message);
}
public class Log : ILog
{
public void WriteLog(string message)
{
// Implementation details...
}
}
public class Test
{
private readonly ILog _obj;
public Test(ILog obj)
{
_obj = obj;
}
// Other members...
}
builder.Services.AddTransient<ILog, Log>();