什么是依赖倒置原则?为什么它很重要?
当前回答
依赖倒置的良好应用在应用程序的整个体系结构层面上提供了灵活性和稳定性。它将允许您的应用程序更安全、更稳定地发展。
传统分层建筑
传统上,分层架构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)
其他回答
依赖倒置原则的一个更清晰的表述方式是:
封装复杂业务逻辑的模块不应该直接依赖于封装业务逻辑的其他模块。相反,它们应该只依赖于简单数据的接口。
也就是说,不是像人们通常做的那样实现你的类逻辑:
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;
}
}
对我来说,官方文章中所描述的依赖倒置原则实际上是一种错误的尝试,它试图提高固有的可重用性较低的模块的可重用性,同时也是一种解决c++语言中的问题的方法。
c++中的问题是头文件通常包含私有字段和方法的声明。因此,如果高级c++模块包含低级模块的头文件,它将取决于该模块的实际实现细节。显然,这不是一件好事。但在今天常用的更现代的语言中,这不是一个问题。
高级模块天生就不如低级模块可重用,因为前者通常比后者更特定于应用程序/上下文。例如,实现UI屏幕的组件是最高级别的,也是非常(完全)特定于应用程序的。试图在不同的应用程序中重用这样的组件是适得其反的,只会导致过度设计。
因此,在组件a的同一级别上创建依赖于组件B(不依赖于组件a)的单独抽象,只有在组件a确实对在不同的应用程序或上下文中重用有用的情况下才能完成。如果不是这样,那么应用DIP将是糟糕的设计。
如果我们可以假定公司的“高级”员工是通过执行他们的计划来获得报酬的,并且这些计划是由许多“低级”员工的计划的综合执行来交付的,那么我们可以说,如果高级员工的计划描述以任何方式与任何低级员工的具体计划耦合在一起,那么这通常是一个糟糕的计划。
If a high level executive has a plan to "improve delivery time", and indicates that an employee in the shipping line must have coffee and do stretches each morning, then that plan is highly coupled and has low cohesion. But if the plan makes no mention of any specific employee, and in fact simply requires "an entity that can perform work is prepared to work", then the plan is loosely coupled and more cohesive: the plans do not overlap and can easily be substituted. Contractors, or robots, can easily replace the employees and the high level's plan remains unchanged.
“高级”在依赖倒置原则中意味着“更重要”。
依赖倒置的良好应用在应用程序的整个体系结构层面上提供了灵活性和稳定性。它将允许您的应用程序更安全、更稳定地发展。
传统分层建筑
传统上,分层架构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)