面向对象设计原则(五):依赖倒置原则(DIP)

2024-04-12 05:48

本文主要是介绍面向对象设计原则(五):依赖倒置原则(DIP),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

面向对象设计原则(五):依赖倒置原则(DIP)

     依赖倒置原则(Dependency Inversion Principle,DIP)也称依赖反转原则,是面向对象设计(OOD)中比较重要、常见的一种,下面来总结依赖倒置原则的知识点,包括:

     1、什么是依赖倒置原则?

     2、为什么需要遵守依赖倒置原则?

     3、在面向对象设计中如何实现依赖倒置原则?

     4、依赖倒置原则的实例应用(包括面向对象程序设计、系统架构、社会活动中的应用)。

1、什么是依赖倒置原则

     依赖倒置原则(Dependency Inversion Principle,DIP)的定义可以总结为以下两点:

     a. 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。

     b. 抽象不应该依赖于细节;细节应该依赖于抽象。

2、为什么需要遵守依赖倒置原则

     依赖倒置原则是一个应用广泛的原则,不仅在面向对象程序框架设计中(是核心原则),还是在架构系统中, 甚至是在社会活动构建组织等方面,都发挥重要作用。

     所以,高层、低层、抽象层可以表示为多种意义:

     高层:客户端、服务消费者、方法调用者……(可能变动、依赖性高)

     低层:服务端、服务提供者、方法实现者,细节实现者……(易变动、通常需要扩展)

     抽象:交互行为、策略、契约、流程、业务模型……(稳定、可重用)

     下图展示了从传统系统到依赖倒置系统的架构演变(点击查看大图):


     1、传统系统

     高层模块直接调用易变动的低层模块。

     优点:

     实现难度小,方便快捷。

     缺点:

     没有抽象,耦合度高;当低层模块变动时,高层模块也得变动;

     高层模块过度依赖低层模块,很难扩展。

     而且这种依赖关系具有传递性,即如果是多层次的调用,最低层改动会影响较高层……直到最高层。

     2、常见的面向抽象系统

     高层模块依赖低层模块提供的抽象,低层模块实现抽象。

     优点:

     这是通常意义的面向接口编程,或开闭原则;

     抽象接口就是低层模块可对外提供的服务。

     一定程度上降低了耦合,使得低层模块易于扩展。

     缺点:

     高层模块还是依赖低层模块,只不过依赖的是低层抽象。

     低层抽象还是有较高变动性,而且还是会传递;

     使得高层模块的可重用性降低。

     3、依赖倒置:第一层境界

     高层模块为它所需要的服务声明一个抽象接口,低层模块实现了这些抽象接口。

     即:低层模块依赖高层模块,依赖倒置了。

     优点:

     首先,还是具有前面"常见的面向抽象系统"的优点:降低了耦合,使得低层模块易于扩展。

     另外,依赖倒置后,高层模块完全不依赖低层模块,不受低层模块易变性影响,增加高略层的可重用性。

     还有,低层模块为高层模块而生,更符合"先有需求,后去实现";而不是低层模块的"假设",这种"假设"通常就是定义说的"抽象依赖细节"。

     缺点:

     低层模块依赖高层模块,受到高层模块的抽象的限制和变动影响,可重用性低。

     另外,高层模块只能使用实现其抽象的低层模块。

     4、依赖倒置:第二层境界

     把高层模块和低层模块交互行为抽象出来,做到互不依赖,都依赖于提取出来的抽象。

     即:无论高层模块或低层模块实现的细节,都依赖于抽象

     优点:

     高层模块和低层模块彻底解耦,都很容易实现扩展;

     而抽象模块具有很高的稳定性、可重用性,对高/低层模块来说才是真正"可依赖的"。

     缺点:

     增加了一层抽象层,增加实现难度;

     对一些简单的调用关系来说,可能是得不偿失的。

     对一些稳定的调用关系,反而增加复杂度,是不正确的。

     5、小结

     每种实现都有其优点和缺点,都有其存在的理由;如果我们认识到这些,在设计就能作出更合理的选择。

     而依赖倒置原则,是构建大型、易扩展、可重用框架/系统的核心原则。

     一般来说,系统中存在违反依赖倒置原则的地方,很可能就是我们需要优化的地方。

3、在面向对象设计中如何实现依赖倒置原则

     前面概括的说明了依赖倒置原则的应用,下面更具体的说明:依赖倒置原则在面向对象设计方面需要怎么做?如下图(点击查看大图)


     1、基础:依赖于抽象

     依赖倒置原则在面向对象框架设计的基础规则:

     依赖于抽象。

     即:

     程序中所有的依赖关系都应该终止于抽象类或者接口中;

     而不应该依赖于具体类。

     根据这个启发式规则,编程时可以这样做:

     (1)、类中的所有成员变量必须是接口或抽象,不应该持有一个指向具体类的引用或指针。

     即所有具体类只能通过接口或抽象类连接。

     不应该:HashMap map;

     应该:Map map;

     (3)、任何类都不应该从具体类派生。

     (4)、任何方法都不应该覆写它的任何基类中已经实现的方法。(里氏替换原则)

     (5)、任何变量实例化都需要实现创建模式(如:工厂方法/模式),或使用依赖注入框架(如:Spring IOC)。

     但通常都会违反该启发规则的情况,比如Java中可以依赖稳定的String类,而不造成损害。

     而我们自己编写的大多数具体类都是不稳定的,通过把它们隐藏在抽象接口的后面,可以隔离它们的不稳定性。

     2、核心:依赖倒置

     虽然抽象接口可以隐藏隔离不稳定性,但类接口必须变化时,还是会破坏抽象接口的隔离性。

     所以,可以由客户类来声明它们需要的服务接口,改变实现抽象接口的类就不会影响到客户了。

依赖倒置原则第一层境界:

     在这种实现中,高级组件和低级组件分布到单独的软件包/库中,其中定义高级组件所需的行为/服务的接口由高级组件的库所有并存在于高级组件的库中。

     通过低级组件实现高级组件的接口,要求低级组件包依赖于编译的高级组件,从而颠倒了常规的依赖关系。

                              

     在这个版本的DIP中,较低层组件对较高级别层的接口的依赖使得较低层组件的重新利用变得困难。

     这种实现反而将"传统的依赖关系从上到下颠倒",从底层到顶端相反。

依赖倒置原则第二层境界:

     将所有层分离成自己的包,更灵活,获得更好的扩展性、重用性、鲁棒性和移植性。

注:

     有些书籍和文章把DIP等同于面向接口编程;

     个人理解是面向接口编程只是DIP的基础,而核心是"依赖倒置"。

4、依赖倒置原则的实例应用

     前面也说到依赖倒置原则是一个应用广泛的原则,不仅在面向对象程序框架设计中(是核心原则),还是在架构系统中, 甚至是在社会活动构建组织等方面,都发挥重要作用。

4-1、Button控制Lamp的案例

     1、通常的方案

     Button事件响应函数中直接调用Lamp相应动作函数。

     这个方案违反了DIP,当Lamp类改变时,Button类会受到影响。

     2、依赖倒置第一境界的方案

     首先,找出潜在的抽象

     它是应用背后的抽象,是那些不随具体细节的改变而改变的真理。

     它是系统内部的系统——它是隐喻(metaphore)。

     这个例子背后的抽象是:

     检测用户的开/关指令并将指令传给目标对象。

     所以,可以有下面这个方案:

     Button关联一个其可以做的控制动作的抽象接口ButtonServer,Lamp实现这个接口。

     优点:      

     这样可以使Button控制那些愿意实现ButtonServer接口的任何设备;

     获得了极大的灵活性,也意味着Button将能够控制还没有创造出来的对象。

     缺点:

      存在一个不友好的约束,需要实现该接口才能被Button控制;

     但还可能被其他不同于Button的对象控制。

     3、依赖倒置第二境界的方案

     上面的方案:Lamp依赖于ButtonServer,但ButtonServer没有依赖于Button。

     对此,还可以进一步改进,Button和ButtonServer可以分开在不同的库中,ButtonServer改名为SwitchableDevice;

     这样SwitchableDevice的使用就不必包含Button的使用。

     这种方案总结:

     接口没有所有者,可以被许多不同的客户使用,并被许多不同的服务都实现。

     接口可以被放置在一个单独的组中,如在Java放在一个单独的package中。

     4、小结

     上面可以看到应用DIP带来的好处:解耦合、易扩展、可重用等。

     相似的,DIP可以应用于任何存在一个类向另一个类发送消息的地方

     但是就这个例子"Button控制Lamp"来说,实际中需要考虑:是不是值得应用DIP。

4-2、DIP在系统架构中的应用

     1、数据存储

     传统:

     业务层在不同数据类型的CURD地方,直接调用不同数据系统(Mysql/Mongo/Rides缓存/本地文件系统)的相关接口,把数据保存到相应系统。

     DIP:

     抽象数据存储层(DAO),DAO模块实现不同数据系统的接口;

     业务层调用DAO层接口传入不同类型数据,DAO层适配到相应数据系统。

     2、消息传输

     通常:

     上层通过调用下层接口,发消息给下层。

     一般情况上层需要同步等待下层处理后的响应。

     DIP:

     通过消息队列(Message Queue)解耦,实现异步传输;

     上层调用MQ的发送消息接口,消息发送到MQ,下层调用MQ获取消息的接口进行消费;

     这时上/下层都可以轻松进行扩展。

     注意:如果上层实时关注下层的处理结果,MQ就不适用。

     3、配置/服务注册中心

     传统:

     下层服务配置保存在配置文件或数据库,改动一些配置或扩展下层服务时可能影响到上层服务;

     如:

     上层服务依赖下层服务的IP、RPC服务地址等等。

     DIP:

     独立出来配置/服务注册中心;

     当下层服务配置改变时,更新到配置心中;配置中心把更新推送到上层服务。

     如:

     针对IP,可以通过DNS,上层使用的是域名,下层服务IP更新时,只需在DNS改动记录指向新的IP。

     针对RPC服务地址,通过服务注册中心(如:ZooKeeper)来发布;上层通过注册中心接口订阅所需服务,当下层通过注册中心接口扩展新注册服务或更新服务地址时,上层服务会收到订阅的服务更新情况。

4-3、DIP在社会活动中的应用

     1、电脑主板

     传统:

     开始的电脑,各组件(内存、显卡等)根据某个厂家的产品直接焊接集成到一起。

     DIP:

     各组件都定义标准接口,主板上预留标准接口,各厂家按标准接口来生产。

     这样就很容易更换不同厂家的产品。

     2、DIP公司

     传统:

     客户直接到某个商家消费,或用户直接使某个商家的服务。

     DIP:

     一些公司构建中间平台,众多商家把自家产品或服务放到平台上,客户通过该平台很容易找到所需的某个产品或服务。

     所谓:一流的企业做标准(中间抽象层),二流的企业做品牌(高层),三流的企业做产品(下层);

     DIP公司就是一流的企业,如:阿里巴巴/淘宝、支付宝、高通……

5、总结

     依赖倒置原则(Dependency Inversion Principle,DIP):

     a. 高层模块不应该依赖于低层模块,两者都应该依赖于抽象。

     b. 抽象不应该依赖于细节;细节应该依赖于抽象。

     即:

     传统系统:高层模块依赖低层模块。

     DIP第一层境界:低层模块依赖高层模块。

     DIP第二层境界:无论高层模块或低层模块实现的细节,都依赖于独立出来的抽象层。

 

     依赖倒置原则应用广泛,在面向对象程序框架设计中(是核心原则)、架构系统中、在社会活动构建组织等方面,都发挥重要作用。

     DIP在面向对象程序设计中,面向接口编程是基础,而核心是"依赖倒置"。

     一般来说,系统中存在违反DIP的地方,很可能就是我们需要优化的地方。

     但实际中需要考虑应用DIP的代价。

 

    到这里,我们对依赖倒置原则有了一个大体的了解,后面我们将了解其他的面向对象设计原则......

 

【参考资料】

1、《敏捷软件开发:原则、模式与实践》第11章 依赖倒置原则(DIP)

2、维基百科"Dependency Inversion Principle"

3、《代码大全》第二版 第5章 软件构件中的设计

4、《Java与模式》第8章 依赖倒转原则(DIP)

5、《大话设计模式》第5章 会修电脑不会修收音机?—依赖倒转原则

6、《Design Patterns》GoF

7、《面向对象分析与设计》第3版

这篇关于面向对象设计原则(五):依赖倒置原则(DIP)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/896269

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

每天认识几个maven依赖(ActiveMQ+activemq-jaxb+activesoap+activespace+adarwin)

八、ActiveMQ 1、是什么? ActiveMQ 是一个开源的消息中间件(Message Broker),由 Apache 软件基金会开发和维护。它实现了 Java 消息服务(Java Message Service, JMS)规范,并支持多种消息传递协议,包括 AMQP、MQTT 和 OpenWire 等。 2、有什么用? 可靠性:ActiveMQ 提供了消息持久性和事务支持,确保消

怎么让1台电脑共享给7人同时流畅设计

在当今的创意设计与数字内容生产领域,图形工作站以其强大的计算能力、专业的图形处理能力和稳定的系统性能,成为了众多设计师、动画师、视频编辑师等创意工作者的必备工具。 设计团队面临资源有限,比如只有一台高性能电脑时,如何高效地让七人同时流畅地进行设计工作,便成为了一个亟待解决的问题。 一、硬件升级与配置 1.高性能处理器(CPU):选择多核、高线程的处理器,例如Intel的至强系列或AMD的Ry

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是

SprinBoot+Vue网络商城海鲜市场的设计与实现

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 application.yml3.5 SpringbootApplication3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍:CSDN认证博客专家,CSDN平台Java领域优质创作者,全网30w+

JVM内存调优原则及几种JVM内存调优方法

JVM内存调优原则及几种JVM内存调优方法 1、堆大小设置。 2、回收器选择。   1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。   2、对JVM内存的系统级的调优主要的目的是减少

单片机毕业设计基于单片机的智能门禁系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍程序代码部分参考 设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订

Spring的设计⽬标——《Spring技术内幕》

读《Spring技术内幕》第二版,计文柯著。 如果我们要简要地描述Spring的设计⽬标,可以这么说,Spring为开发者提供的是⼀个⼀站式的轻量级应⽤开发框架(平台)。 作为平台,Spring抽象了我们在 许多应⽤开发中遇到的共性问题;同时,作为⼀个轻量级的应⽤开发框架,Spring和传统的J2EE开发相⽐,有其⾃⾝的特点。 通过这些⾃⾝的特点,Spring充分体现了它的设计理念:在