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


当前回答

对我来说,官方文章中所描述的依赖倒置原则实际上是一种错误的尝试,它试图提高固有的可重用性较低的模块的可重用性,同时也是一种解决c++语言中的问题的方法。

c++中的问题是头文件通常包含私有字段和方法的声明。因此,如果高级c++模块包含低级模块的头文件,它将取决于该模块的实际实现细节。显然,这不是一件好事。但在今天常用的更现代的语言中,这不是一个问题。

高级模块天生就不如低级模块可重用,因为前者通常比后者更特定于应用程序/上下文。例如,实现UI屏幕的组件是最高级别的,也是非常(完全)特定于应用程序的。试图在不同的应用程序中重用这样的组件是适得其反的,只会导致过度设计。

因此,在组件a的同一级别上创建依赖于组件B(不依赖于组件a)的单独抽象,只有在组件a确实对在不同的应用程序或上下文中重用有用的情况下才能完成。如果不是这样,那么应用DIP将是糟糕的设计。

其他回答

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

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

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

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

依赖倒置原理(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]

对我来说,官方文章中所描述的依赖倒置原则实际上是一种错误的尝试,它试图提高固有的可重用性较低的模块的可重用性,同时也是一种解决c++语言中的问题的方法。

c++中的问题是头文件通常包含私有字段和方法的声明。因此,如果高级c++模块包含低级模块的头文件,它将取决于该模块的实际实现细节。显然,这不是一件好事。但在今天常用的更现代的语言中,这不是一个问题。

高级模块天生就不如低级模块可重用,因为前者通常比后者更特定于应用程序/上下文。例如,实现UI屏幕的组件是最高级别的,也是非常(完全)特定于应用程序的。试图在不同的应用程序中重用这样的组件是适得其反的,只会导致过度设计。

因此,在组件a的同一级别上创建依赖于组件B(不依赖于组件a)的单独抽象,只有在组件a确实对在不同的应用程序或上下文中重用有用的情况下才能完成。如果不是这样,那么应用DIP将是糟糕的设计。

基本上它说:

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

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

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

依赖倒置原则指出:

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

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