php 依赖注入和依赖反转的区别

eagi6jfj  于 2023-09-29  发布在  PHP
关注(0)|答案(5)|浏览(110)

有两种设计模式,即依赖注入和依赖反转,网上有文章试图解释它们的区别。但用更简单的语言解释它的必要性仍然存在。有没有人可以上去?
我需要在PHP中理解它。

ars1skjm

ars1skjm1#

  • (注:这个答案与语言无关,虽然问题特别提到了PHP,但由于对PHP不熟悉,我没有提供任何PHP示例)*。

术语-依赖和耦合

在面向对象编程的上下文中,依赖项是任何其他的对象类型,类与之有直接的关系。当一个类直接依赖于另一个对象类型时,它可以被描述为与该类型耦合。
一般来说,类使用的 any 类型在某种程度上是依赖项。一个类依赖于另一个类型有许多不同的方式,包括:

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

类与其依赖项之间的关系越强,耦合就越紧密;因此,当一个类直接依赖于另一个具体类时(例如继承创建了对基类的直接依赖的情况,或者构造函数为其示例变量创建了新对象的情况),对该直接依赖的任何未来更改都更有可能以蝴蝶效应的方式“涟漪”。

Injection与Inversion的区别

1.依赖性反转原则(DIP)是一个软件设计 * 准则 *,它归结为关于de-coupling a class from its concrete dependencies的两个建议:
1.高级模块不应该依赖于低级模块。两者都应该依赖于抽象。
1.“抽象不应该依赖于细节。细节应该依赖于抽象。
或者,更简洁地说:

  • 依赖注入是一种用于填充类的示例变量的实现技术。
  • 依赖倒置是一个通用的设计准则,它建议类应该只与高级抽象有直接的关系。

依赖注入和控制反转(IoC)

依赖注入通过确保类从不负责创建或提供自己的依赖关系(因此也不负责这些依赖关系的生命周期)来应用IoC原则。
然而,*IoC不是依赖注入 * -事实上,IoC作为一个原则与依赖或依赖注入本身没有特别的关系;依赖注入是一种基于IoC原则的设计模式。
IoC在许多其他上下文中也可以看到,包括那些与对象创建或依赖关系完全无关的上下文中,例如通过中介或消息泵传递消息以触发事件处理程序。IoC的其他(不相关)示例包括:

  • 使用事件处理函数/方法来处理鼠标/键盘输入事件的窗口应用程序。
  • 一个使用Controller Actions处理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更高级别的抽象。将Foo更改为依赖于IRecordReader而不是SqlRecordReader符合DIP准则。

DIP指南为什么有用

关键字是 guideline -依赖反转为程序设计添加了间接性。添加任何类型的间接的明显缺点是复杂性(即人类理解正在发生的事情所需的认知“负荷”)增加。
在许多情况下,间接可以使代码更容易维护(修复bug,添加增强功能),但是:
在最后一个例子中,Foo * 可能 * 接收一个SqlRecordReader,或者一个SoapRecordReader,或者一个FileRecordReader,甚至可能是一个MockRecordReader进行单元测试--关键是它不知道或不关心任何关于IRecordReader的不同可能实现--当然,这些实现必须符合Liskov Substitution Principle
此外,它避免了潜在的肮脏场景,即急于让某些东西工作的开发人员可能会考虑通过从基类SqlRecordReader继承SoapRecordReaderFileRecordReader来尝试“捏造”Liskov原则。
更糟糕的是,一个没有经验的开发人员甚至可能会改变SqlRecordReader本身,使类不仅具有用于SQL的逻辑,而且还具有用于SOAP端点、文件系统和其他任何可能需要的逻辑。(这种事情在真实的世界中经常发生-特别是在维护较差的代码中,并且几乎总是Code Smell

7uzetpgm

7uzetpgm2#

参见本文here
作者用简单的话来区分这两者。**Dependency Injection ==“Gimme it”和Dependency Inversion ==“Someone take care of this for me,somehow."在依赖反转原理中,高层模块是抽象的所有者。因此,细节(抽象的实现)取决于抽象,因此取决于高级模块。倒卧!依赖注入不同。抽象可能不会被高级模块保存。因此,给予较高级对象的抽象可能不限于高级模块的需要。
依赖倒置:
你有一个更高层次的模块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。

ttcibm8c

ttcibm8c3#

依赖注入是对象提供其他对象依赖的能力。简单地说,它意味着另一个东西依赖于另一个东西。示例类A使用了类B的一些函数,现在类A需要创建类B的示例,这里使用DI来使用。
IOC是颠倒不同的职责,例如你需要在家工作,但你需要做饭吃现在列出的在家做饭,你可以在网上订购,它是提供给你的门一步,这意味着你可以专注于你的工作。在这里,你把烹饪的责任倒置给了网上餐厅。

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

11dmarpk

11dmarpk4#

依赖注入是实现控制反转的一种方法(我假设你指的是依赖反转),所以这两者并不像DI是IoC的一种特殊化那样存在竞争。其他常见的实现IoC的方法包括使用工厂或服务定位器模式。

tjvv9vkg

tjvv9vkg5#

依赖倒置:如何设计类和接口(高层应该依赖于抽象,而不是底层细节)。

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

依赖注入:将这些依赖项提供或“注入”到类中的机制(手动或使用框架)在一些DI设置代码中,如ASP.NET Core中的Program.cs,例如实现依赖注入:

builder.Services.AddTransient<ILog, Log>();

相关问题