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


当前回答

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

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

依赖倒置原则指出:

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

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

其他回答

依赖倒置的重点是制作可重用的软件。

其思想是,两段代码不再相互依赖,而是依赖于一些抽象的接口。然后你可以在没有另一块的情况下重复使用其中的任何一块。

最常见的实现方式是通过控制反转(IoC)容器,如Java中的Spring。在这个模型中,对象的属性是通过XML配置来设置的,而不是由对象自己去寻找它们的依赖项。

想象一下这个伪代码……

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

MyClass直接依赖于Service类和ServiceLocator类。如果你想在另一个应用程序中使用它,这两个都需要。现在想象一下……

public class MyClass
{
  public IService myService;
}

现在,MyClass依赖于一个单独的接口,IService接口。我们让IoC容器实际设置那个变量的值。

所以现在,MyClass可以很容易地在其他项目中重用,而不会带来其他两个类的依赖关系。

更好的是,您不必拖动MyService的依赖项,以及这些依赖项的依赖项,以及…好吧,你懂的。

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

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

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

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

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

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

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

没有依赖反转:

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

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

传统分层建筑

传统上,分层架构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)

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

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

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

使用IoC的类似代码:

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

国际奥委会的好处是:

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