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


当前回答

依赖倒置原则的一个更清晰的表述方式是:

封装复杂业务逻辑的模块不应该直接依赖于封装业务逻辑的其他模块。相反,它们应该只依赖于简单数据的接口。

也就是说,不是像人们通常做的那样实现你的类逻辑:

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

你应该这样做:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

Data和DataFromDependency应该与Logic在同一个模块中,而不是与Dependency在一起。

为什么要这么做?

这两个业务逻辑模块现在已解耦。当Dependency改变时,你不需要改变Logic。 理解Logic所做的是一个简单得多的任务:它只对看起来像ADT的东西起作用。 现在可以更容易地检验逻辑。现在,您可以直接实例化假数据Data并将其传入。不需要模拟或复杂的测试脚手架。

其他回答

我可以在上面的回答中看到很好的解释。然而,我想用简单的例子提供一些简单的解释。

依赖倒置原则允许程序员删除硬编码的依赖项,这样应用程序就变得松散耦合和可扩展。

如何实现这一点:通过抽象

没有依赖反转:

 class Student {
    private Address address;

    public Student() {
        this.address = new Address();
    }
}
class Address{
    private String perminentAddress;
    private String currentAdrress;

    public Address() {
    }
} 

在上面的代码片段中,地址对象是硬编码的。相反,如果我们可以使用依赖倒置,并通过传递构造函数或setter方法注入地址对象。让我们来看看。

使用依赖倒置:

class Student{
    private Address address;

    public Student(Address address) {
        this.address = address;
    }
    //or
    public void setAddress(Address address) {
        this.address = address;
    }
}

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

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

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

使用IoC的类似代码:

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

国际奥委会的好处是:

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

依赖倒置原理(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原则的“松散耦合设计”。

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

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

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

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

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

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