什么是依赖倒置原则?为什么它很重要?


当前回答

基本上它说:

类应该依赖于抽象(例如接口,抽象类),而不是特定的细节(实现)。

其他回答

依赖倒置原理(DIP)

它是SOLID[About]的一部分,SOLID[About]是OOD的一部分,由Bob叔叔介绍。它是关于类(层…)之间的松散耦合。类不应该依赖于具体的实现,类应该依赖于抽象/接口

问题:

//A -> B
class A {
  B b

  func foo() {
     b = B();
  }
}

解决方案:

//A -> IB <|- B
//client[A -> IB] <|- B is the Inversion 
class A {
  IB ib // An abstraction between High level module A and low level module B

  func foo() {
     ib = B()
  }
}

现在A不依赖于B(一对一),现在A依赖于B实现的接口IB,这意味着A依赖于IB的多重实现(一对多)

[DIP vs DI vs IoC]

当我们设计软件应用程序时,我们可以考虑低层类——实现基本和主要操作的类(磁盘访问、网络协议……)和高层类——封装复杂逻辑的类(业务流……)。

最后一个依赖于低级类。实现这种结构的自然方法是编写低级类,一旦我们有了它们,就编写复杂的高级类。由于高级类是根据其他类定义的,这似乎是一种合乎逻辑的方法。但这不是一个灵活的设计。如果我们需要替换一个低级类,会发生什么?

依赖倒置原则指出:

高级模块不应该依赖于低级模块。两者都应该依赖于抽象。 抽象不应该依赖于细节。细节应该依赖于抽象。

这个原则试图“颠倒”传统的概念,即软件中的高级模块应该依赖于低级模块。在这里,高级模块拥有由低级模块实现的抽象(例如,决定接口的方法)。因此,较低级别的模块依赖于较高级别的模块。

这里的其他人已经给出了很好的答案和例子。

DIP之所以重要,是因为它保证了oo原则的“松散耦合设计”。

软件中的对象不应该进入一个层次结构,其中一些对象是顶级对象,依赖于低级对象。底层对象的变化会波及到顶层对象,这使得软件非常容易发生变化。

你希望你的“顶级”对象非常稳定,不容易改变,因此你需要颠倒依赖关系。

控制反转(IoC)是一种设计模式,在这种模式下,对象通过外部框架获得其依赖项,而不是向框架请求其依赖项。

使用传统查找的伪代码示例:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

使用IoC的类似代码:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

国际奥委会的好处是:

您不依赖于中心 框架,所以这可以改变如果 想要的。 因为对象是被创建的 以注射方式使用为佳 接口,很容易创建单元 替换依赖项的测试 模拟版本。 解耦代码。

依赖倒置的良好应用在应用程序的整个体系结构层面上提供了灵活性和稳定性。它将允许您的应用程序更安全、更稳定地发展。

传统分层建筑

传统上,分层架构UI依赖于业务层,而业务层又依赖于数据访问层。

您必须了解层、包或库。让我们看看代码是怎样的。

我们将有一个用于数据访问层的库或包。

// DataAccessLayer.dll
public class ProductDAO {

}

以及依赖于数据访问层的另一个库或包层业务逻辑。

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

带有依赖倒置的分层体系结构

依赖倒置表明:

高级模块不应该依赖于低级模块。两者都应该依赖于抽象。

抽象不应该依赖于细节。细节应该依赖于抽象。

什么是高级模块和低级模块?思考模块,如库或包,高级模块将是那些传统上具有依赖关系的模块,而它们所依赖的低级模块。

换句话说,模块高层是调用操作的地方,而模块低层是执行操作的地方。

从这一原则可以得出一个合理的结论,即具象之间不应存在依赖关系,而必须对抽象存在依赖关系。但根据我们所采取的方法,我们可能会误用投资依赖依赖,而是一种抽象。

假设我们对代码进行如下调整:

我们将为定义抽象的数据访问层提供一个库或包。

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

以及依赖于数据访问层的另一个库或包层业务逻辑。

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

尽管我们依赖于抽象,但业务和数据访问之间的依赖关系保持不变。

要获得依赖项反转,持久性接口必须定义在高级逻辑或域所在的模块或包中,而不是定义在低级模块中。

首先定义什么是领域层,其通信的抽象是定义持久化。

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

在持久化层依赖于域之后,如果定义了依赖项,就可以进行反转。

// Persistence.dll
public class ProductDAO : IProductRepository{

}

(来源:xurxodev.com)

深化原则

重要的是要吸收好概念,深化目的和效益。如果我们机械地停留在学习典型案例存储库中,我们将无法确定在哪里可以应用依赖原则。

但是为什么要反转依赖关系呢?除了具体的例子,主要目标是什么?

这通常允许最稳定的东西,不依赖于不太稳定的东西,更频繁地变化。

持久化类型的更改(访问相同数据库的数据库或技术)比设计用于与持久化通信的域逻辑或操作更容易。因此,这种依赖是相反的,因为如果发生这种更改,则更容易更改持久性。这样我们就不需要改变定义域了。域层是所有层中最稳定的,这就是为什么它不应该依赖于任何东西。

但不只是这个存储库示例。有许多应用此原则的场景,并且有基于此原则的架构。

体系结构

在某些架构中,依赖倒置是其定义的关键。在所有的域中,它是最重要的,它是用来指示域和其他被定义的包或库之间的通信协议的抽象。

干净的建筑

在Clean体系结构中,域位于中心,如果您查看指示依赖关系的箭头方向,就可以清楚地看到哪些是最重要和最稳定的层。外层被认为是不稳定的工具,因此避免依赖它们。

(来源:8 thlight.com)

六角结构

这与六边形体系结构发生的情况相同,其中域也位于中心部分,端口是从多米诺骨牌向外通信的抽象。这里很明显,域是最稳定的,传统的依赖关系是颠倒的。

(来源:pragprog.com)