依赖倒置原则(DIP)

1. 定义

(1)高层模块不应依赖于低层模块,两者都应该依赖于抽象。
(2)抽象不应该依赖于细节,细节应该依赖于抽象。

为什么是“倒置”这个词?

这是由于许多传统的软件开发方法,比如结构化分析和设计,总是倾向于创建一些高层依赖于低层模块、策略依赖于细节的软件结构。
实际上这些方法的目的之一就是要定义程序层次结构,该层次结构描述了高层模块怎样调用低层模块。
一个设计良好的面向对象的程序,其依赖于程序结构相对于传统的过程式方法设计的通常结构而言就是被“倒置”了。

高层模块包含了一个应用程序中的重要的策略选择和业务模块。正是这些高层模块才使得其所在的应用程序区别于其他。然而,如果这些高层模块依赖于低层模块,那么低层模块的改动就会直接影响到高层模块,从而迫使它们依次做出改动。

这种情况是非常荒谬的,本应该高层的策略设置模块去影响低层的细节实现模块。包含高层业务规则的模块应该优先并独立于包含实现细节的模块。无论如何高层模块都不应该依赖于低层模块。

2. 层次化

所有结构良好的面向对象架构都具有清晰的层次定义,每个层次通过一个定义良好的、受控的接口向外提供一组内聚的服务。

高层Policy使用低层的Mechanism,而Mechanism使用了更细节的Utility。这看起来似乎是正确的,然而存在一个隐伏的错误特征:

Policy对于其下一直到Utility的改动都是敏感的。这种依赖关系是传递的。Policy依赖于某些Utility层次。因此,Policy传递性地依赖于Utility。

上图展示了一个更为合适的模型,每个较高层次都为它所需要的服务声明一个抽象接口,较低的层次是吸纳了这些抽象接口,每个高层类都通过该抽象接口使用下一层,这样高层就不依赖于低层。低层反而依赖于在高层中声明的抽象服务接口。这不仅解除了Policy Layer对Utility Layer的传递依赖关系,甚至也解除了Policy Layer对于Mechanism Layer的依赖关系。

请注意这里的倒置不仅仅是依赖关系的倒置,它也是接口所有权的倒置。通常会认为工具库应该拥有自己的接口。但是当应用了DIP时,往往是客户拥有抽象接口,而它们的服务者则从这些抽象接口派生。

2.1 倒置的接口所有权

低层模块实现了在高层模块中声明并被高层模块调用的接口。
通过这种倒置的接口所有权,对于MechanismLayer或UtilityLayer的任何改动都不会再影响到PolicyLayer。而且,PolicyLayer可以在定义了符合PolicyServiceInterface的任何上下文中重用。这样,通过倒置这些依赖关系,创建了一个更灵活、更持久、更易改变的结构。

2.2 依赖于抽象

程序中所有的依赖关系都应该终止于抽象类或接口,更这个启发式规则,可知:
(1)任何变量都不应该持有一个指向具体类的指针或引用
(2)任何类都不应该从具体类派生
(3)任何方法都不应该覆写它的任何基类中的已经实现了的方法
当然,每个程序中都会有违反该启发规则的情况,有事必须要创建具体类的实例,而创建这些实例的模块将会依赖于它们。
此外,该启发规则对于那些虽是具体但却稳定的类来说似乎不太合理。如果一个具体类不太会改变,并且也不会创建其他类似的派生类,那么依赖于它并不会造成损害。
应用程序中所编写的大多数具体类都是不稳定的。我们不想依赖于这些不稳定的具体类。通过把它们隐藏在抽象接口的后面,可以隔离它们的不稳定性。
由客户类来声明它们需要的服务接口,那么仅当客户需要时才会对接口进行改变。这样,改变实现抽象接口的类就不会影响到客户。

3. 一个简单的例子

依赖倒置可以应用于任何存在一个类向另一个类发送消息的地方。
例如:Button对象和Lamp对象之间的情形。
Button对象感知外部环境的变化。当接收到Poll消息时,它会判断是否被用户“按下”。它不关心是通过什么样的机制去感知的。如何设计一个用Button对象控制Lamp对象的系统呢?

上图展示了一个不成熟的设计。Button对象接收Poll消息,判断按钮是否被按下,接着简单地发送turnOn或turnOff消息给Lamp对象。Button类直接依赖于Lamp类。这个依赖关系意味着当Lamp类改变时,Button类会受到影响。此外,想要重用Button来控制一个Motor对象是不可能的。在这个设计中,Button控制着Lamp对象,并且也只能控制Lamp对象。

class Button {
	Lamp itsLamp;
	void poll() {
	    itsLamp.turnOn();
	}
}

这个方案违反了DIP,应用程序的高层策略没有和低层实现分离。抽象没有和具体细节分离。没有这种分离,高层策略就自动地依赖于低层模块,抽象就自动地依赖于具体细节。

3.1 找到潜在的抽象  

什么是高层策略呢?它是应用背后的抽象,是那些不随具体细节的改变而改变的真理。
在Button/Lamp例子中,背后的抽象是检测用户的开关并将指令传给目标对象。
用什么机制检测用户的指令呢?无关紧要,目标对象是什么?同样无关紧要,这些都是不会影响到抽象的具体细

通过倒置对Lamp对象的依赖关系,可以改进设计。

可以看出Button现在和一个ButtonServer的接口关联起来。ButtonServer接口提供了一些抽象方法,Button可以使用这些方法来开启或关掉一些东西。Lamp实现了ButtonServer接口。这样,Lamp现在是依赖于别的东西了,而不是被依赖了。

设计可以使Button控制哪些愿意实现ButtonServer接口的任何设备,这赋予我们极大的灵活性。同时也意味着Button对象将能够控制还没有被创造出来的对象。

4. 总结

使用传统的过程化程序设计所创建出来的依赖关系结构,策略是依赖于细节的。只是糟糕的,因为这样会使策略收到细节改变的影响。面向对象的程序设计导致了依赖关系结构,使得细节和策略都依赖于抽象,并且常常是客户用于服务接口

事实上,这种依赖关系的倒置正是好的面向对象设计的标志所在。使用何种语言来编写程序时无关紧要的。如果程序的依赖关系是倒置的,它就是面向对象的设计。如果程序的依赖关系不是倒置的,它就是过程化的设计。

依赖倒置原则是实现许多面向对象技术所宣称的好处的基本低层机制。它的正确应用对于创建可重用的框架来说是必须的。同时它对于构建在变化面前富有弹性的代码也是非常重要的。由于抽象和细节被彼此距离,所以代码也非常容易维护。

原文地址:https://www.cnblogs.com/lujiango/p/11549988.html

时间: 2024-10-04 00:58:50

依赖倒置原则(DIP)的相关文章

Java 设计模式(十二) 依赖倒置原则(DIP)

依赖倒置原则(Dependence Inversion Principle) 依赖倒置原则(DIP)的基本概念 原始定义 高层模块不应该依赖低层模块,两者都应该依赖其抽象 抽象不应该依赖细节 细节应该依赖抽象 Java中的具体含义 模块间的依赖通过抽象发生 实现类之间不发生直接的依赖关系 其依赖关系通过接口或者抽象类产生 接口或抽象类不依赖于具体实现 实现类依赖接口或抽象类 依赖倒置(DIP)的好处 采用DIP可以减少类之间的耦合性,提高稳定性,降低并行开发带来的风险,提高代码的可读性和可维护性

对依赖倒置原则(DIP)及Ioc、DI、Ioc容器的一些理解

.概述 所谓依赖倒置原则(Dependence Inversion Principle)就是要依赖于抽象,不要依赖于具体.简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合,并由此引申出IoC.DI以及Ioc容器等概念. 2.意图 面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本. 面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节

深入理解JavaScript系列(22):S.O.L.I.D五大原则之依赖倒置原则DIP

前言 本章我们要讲解的是S.O.L.I.D五大原则JavaScript语言实现的第5篇,依赖倒置原则LSP(The Dependency Inversion Principle ). 英文原文:http://freshbrewedcode.com/derekgreer/2012/01/22/solid-javascript-the-dependency-inversion-principle/ 依赖倒置原则 依赖倒置原则的描述是: A. High-level modules should not

对依赖倒置原则(DIP)及Ioc、DI、Ioc容器的一些理解(转)

所谓依赖倒置原则(Dependence Inversion Principle)就是要依赖于抽象,不要依赖于具体.简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合,并由此引申出IoC.DI以及Ioc容器等概念. 面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本. 面向对象的开发很好的解决了这个问题,一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象.即使

C#软件设计——小话设计模式原则之:依赖倒置原则DIP

前言:很久之前就想动笔总结下关于软件设计的一些原则,或者说是设计模式的一些原则,奈何被各种bootstrap组件所吸引,一直抽不开身.群里面有朋友问博主是否改行做前端了,呵呵,其实博主是想做“全战”,即各方便都有战斗力.关于设计模式,作为程序猿的我们肯定都不陌生.博主的理解,所谓设计模式就是前人总结下来的一些对于某些特定使用场景非常适用的优秀的设计思路,“前人栽树,后人乘凉”,作为后来者的我们就有福了,当我们遇到类似的应用场景的时候就可以直接使用了.关于设计模式的原则,博主将会在接下来的几篇里面

C#编程:依赖倒置原则DIP

一.前言 我们先来看看传统的三层架构,如下图所示: 从上图中我们可以看到:在传统的三层架构中,层与层之间是相互依赖的,UI层依赖于BLL层,BLL层依赖于DAL层.分层的目的是为了实现“高内聚.低耦合”.传统的三层架构只有高内聚没有低耦合,层与层之间是一种强依赖的关系,这也是传统三层架构的一种缺点.这种自上而下的依赖关系会导致级联修改,如果低层发生变化,可能上面所有的层都需要去修改,而且这种传统的三层架构也很难实现团队的协同开发,因为上层功能取决于下层功能的实现,下面功能如果没有开发完成,则上层

敏捷软件开发:原则、模式与实践——第11章 DIP:依赖倒置原则

第11章 DIP:依赖倒置原则 DIP:依赖倒置原则: a.高层模块不应该依赖于低层模块.二者都应该依赖于抽象. b.抽象不应该依赖于细节.细节应该依赖于抽象. 11.1 层次化 下图展示了一个简单的层次化方案: 高层的Policy层使用了低层的Mechanism层,而Mechanism层又使用了更细节的Utility层.它存在一个隐伏的错误特征,那就是:Policy层对于其下一直到Utility层的改动都是敏感的.依赖关系是传递的. 下图展示了一个更为合适的模型: 每个较高层次都为它所需要的服

六大设计原则(三)DIP依赖倒置原则

原文:六大设计原则(三)DIP依赖倒置原则 依赖倒置原则DIP(Dependence Inversion Principle) 依赖倒置原则的含义 高层模块不能依赖低层模块,二者都应该依赖其抽象. 抽象不应该依赖于细节. 细节应该依赖抽象. 什么是高层模块?低层模块? 每一个原子逻辑就是低层模块,原子逻辑再组就是高层模块. 什么是抽象和细节? 抽象是抽象类,不可被实例化. 细节是实现类,比如实现的接口或继承抽象类的子类,可以被实例化. 表现在Java语言中就是面向接口编程 模块间的依赖是通过抽象

设计模式六大原则---依赖倒置原则(DIP)

定义 依赖倒置原则(Dependency Inversion Principle) 核心思想:依赖于抽象     具体体现: 体现一:高层模块不应该依赖低层模块.两个都应该依赖抽象. 体现二:抽象不应该依赖细节.细节应该依赖抽象. 依赖倒置原则告诉我们:细节是多变的,而抽象是相对稳定的.所以我们编程的时候要注重抽象的编程,而非细节编程. 实例 1.AGP插槽:主板和显卡之间的关系的抽象.主板和显卡通常是使用AGP插槽来连接的,这样,只要接口适配,不管是主板还是显卡更换,都不是问题. 2.驾照:司

敏捷软件开发 – DIP 依赖倒置原则

DIP 依赖倒置原则 高层模块不应该依赖于低层模块.二者都应该依赖于抽象. 抽象不应该依赖于细节.细节应该依赖于抽象. 依赖于低层模块的高层模块意味着什么?正是高层模块包含了应用程序中重要的策略选择和业务模型.这些高层模块使得其所在的应用程序区别于其他.然而,如果这些高层模块依赖于低层模块,那么对于低层模块的改动会直接影响到高层模块,从而迫使它们依次做出改动.如果高层模块独立于低层模块,那么高层模块就可以非常容易地被重用.该原则是框架设计的核心原则. 层次化 糟糕的层次关系. 更为适合的模型.每