面向对象设计原则之依赖倒置原则

目录

  • 定义
    • 原始定义
    • 进一步的理解
  • 作用
  • 实现方法
  • 代码示例

面向对象设计原则之开-闭原则
面向对象设计原则之里式替换原则
面向对象设计原则之依赖倒置原则
面向对象设计原则之单一职责原则

定义

依赖倒置原则(Dependence Inversion Principle),缩写为DIP。

原始定义

High level modules should not depend upon low level modules. Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions
翻译一下:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。

进一步的理解

到底什么“倒置”,要理解什么是倒置,我们先理解一下“正置”,即正常的依赖是什么样子的。比如我们经典的三层架构,controller层调用BL层,BL层调用DAO层。由于每一层都是依赖于下层的实现,这样当某一层的结构发生变化时,它的上层就不得不也要发生改变,比如我们DAO里面逻辑发生了变化,可能会导致BL和Controller层都随之发生变化,这种架构是非常荒谬的!

好,这个时候如果我们换一种设计思路,高层模块不直接依赖低层的实现,而是依赖于低层模块的抽象,具体表现为我们增加一个IBL层,里面定义业务逻辑的接口,controller层依赖于IBL层,BL层实现IBL里面的接口,所有具体的业务逻辑则实现在BL里面,这个时候如果我们BL里面的逻辑发生变化,只要接口的行为不变,上层Controller里面就不用发生任何变化。

以上我们引入面向接口编程的概念,增加了接口IBL层,那倒置到底该如何理解,难道依赖抽象,面向接口就是倒置了?

我觉得可以从软件项目越来越大,开发团队人员越来越多的发展现状来理解,高层A依赖于低层B,即A要调用B提供的方法,那么在B开发完成之前,A层是没发开发,或者开发完没法编译通过和单元测试的。修改为高层A依赖于抽象层C,抽象层C是属于A层的,即由A层来规定抽象层C的接口规范,低层B也依赖于抽象层C来具体实现C中的接口,因此通过引入C层,来达到了“倒置”。通过该倒置,引入C层来规范,A和B 可以同时 来开发,不必相互等待(依赖)。这里的倒置,既有模块依赖上的倒置,更有在解决问题时,思考和规划上的倒置,即要先进行良好的顶层规划设计,约定好接口规范,而具体的逻辑编写都是基于规范的具体而已。

作用

  • 可以减少类间的耦合性、提高系统稳定性。
  • 提高代码可读性和可维护性,可降低修改程序所造成的风险。
  • 可以减少并行开发引起的风险。

实现方法

主要就是合理的抽象接口类并定义接口方法。以下代码示例中以 司机驾车为例,结合代码重构过程来具体说明。

代码示例

司机驾驶奔驰车的类图,起初 我们设计的都是具体实现类,司机类依赖于奔驰车类,Client类中有司机,有车,可以具体创建对象来使用了。

在这里插入图片描述

package com.will.tools.model.dip;public class Benz {public void run(){System.out.println("奔驰汽车跑起来...");}
}

奔驰车可提供一个方法run,代表车辆运行。

package com.will.tools.model.dip;public class Driver {public void drive(Benz benz){benz.run();}
}

开车,调用奔驰车的run方法。

package com.will.tools.model.dip;public class Client {public static void main(String[] args) {Driver guojing = new Driver();Benz benz = new Benz();//郭靖开奔驰guojing.drive(benz);}
}

Client创建 司机郭靖和奔驰车,并让郭靖开奔驰车。

现在来了新需求:郭靖司机不仅要开奔驰车,还要开宝马车,又该怎么实现呢?

先把宝马车创建出来,如下

package com.will.tools.model.dip;

public class BMW {public void run(){System.out.println("宝马汽车跑起来...");}
}

宝马车产生了,但郭靖却没有办法开起来,为什么?
郭靖(Driver)没有开动宝马车的方法,一个拿有C1驾照的司机竟然只能开奔驰车而不能开宝马车,这太不合理了!在现实世界都不允许这样干,何况程序还是对现实世界的抽象呢。

这说明我们的设计出了问题,司机类和奔驰车类紧耦合了,导致系统可维护性和可读性降低。这里只是增加了一个车类,却要修改司机类,被依赖者变更了,却需要让依赖者来承担修改成本,这没有稳定性可言。

另外,对于并行开发的风险也很大,没有奔驰车类,司机类根本编译不过去。
因此,我们重构一下,引入DIP,如下:
在这里插入图片描述
建立两个接口:IDriver和ICar,分别定义了司机和汽车的各个职能,司机就是驾驶汽车,必须实现drive()方法。汽车就是能run。

package com.will.tools.model.dip;public interface ICar {void run();
}
package com.will.tools.model.dip;public interface IDriver {void drive(ICar car);
}
package com.will.tools.model.dip;public class Driver implements IDriver {@Overridepublic void drive(ICar car) {car.run();}
}

接口只是一个抽象化的概念,是对一类事物的最抽象描述,具体的实现代码由相应的实现类来完成。

IDriver通过传入ICar接口实现了抽象之间的依赖关系,Driver实现类也传入了ICar接口,至于到底是哪个型号的Car,需要声明在高层模块。

宝马汽车和奔驰汽车都实现ICar接口,并各自实现run方法。

package com.will.tools.model.dip;public class Benz implements ICar {@Overridepublic void run() {System.out.println("奔驰汽车跑起来...");}
}
package com.will.tools.model.dip;public class BMW implements ICar {@Overridepublic void run() {System.out.println("宝马汽车跑起来...");}
}

业务场景应贯彻“抽象不应依赖细节”,即抽象(ICar接口)不依赖BMW和Benz两个实现类(细节),因此在高层次的模块中应用的都是抽象,传入的参数都是ICar,如下:

package com.will.tools.model.dip;public class Client {public static void main(String[] args) {
//        Driver guojing = new Driver();
//        Benz benz = new Benz();
//        //郭靖开奔驰
//        guojing.drive(benz);IDriver guojing = new Driver();ICar benz = new Benz();guojing.drive(benz);ICar bmw = new BMW();guojing.drive(bmw);}
}

Client属于高层业务逻辑,它对低层模块的依赖都建立在抽象上。guojing的表面类型是IDriver,benz的表面类型是ICar。这时,guojing再要开宝马车的话,就只需要在高层业务类(Client)中直接调用即可,而不用修改Driver类。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/112534.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

互联网Java工程师面试题·Java 总结篇·第十一弹

目录 90、简述一下你了解的设计模式。 91、用 Java 写一个单例类。 92、什么是 UML? 93、UML 中有哪些常用的图? 94、用 Java 写一个冒泡排序。 95、用 Java 写一个折半查找。 90、简述一下你了解的设计模式。 所谓设计模式,就是一套被…

最新Ai写作创作系统源码+Ai绘画系统源码+搭建部署教程+支持GPT4.0+支持Prompt预设应用+思维导图生成

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统AI绘画系统,支持OpenAI GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美,可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署…

2、Kafka 生产者

3.1 生产者消息发送流程 3.1.1 发送原理 在消息发送的过程中,涉及到了两个线程——main 线程和 Sender 线程。在 main 线程 中创建了一个双端队列 RecordAccumulator。main 线程将消息发送给 RecordAccumulator, Sender 线程不断从 RecordAccumulator 中…

OpenCV 笔记(2):图像的属性以及像素相关的操作

Part11. 图像的属性 11.1 Mat 的主要属性 在前文中,我们大致了解了 Mat 的基本结构以及它的创建与赋值。接下来我们通过一个例子,来看看 Mat 所包含的常用属性。 先创建一个 3*4 的四通道的矩阵,并打印出其相关的属性,稍后会详细…

安装Sentinel

大家好今天来安装Sentinel . 安装Sentinel 下载 : 大家可以选择相应版本(最新版本1.8.6) 官网下载地址 : Release v1.8.6 alibaba/Sentinel GitHub 链接:Sentinel_免费高速下载|百度网盘-分享无限制 (baidu.com) 提取码:8eh9 运行 : 将jar包放到任…

Youtrack Linux 安装

我们考虑最后应该使用的是 ZIP 方式的安装。 按照官方的说法如何设置运行 YouTrack 应该是非常简单的。 准备环境 根据官方的说法,我们需要做的就是下载 Zip 包,然后把 Zip 包解压到指定的目录中就可以了。 下载 当前官方的下载地址为:Ge…

基于nodejs+vue中学信息技术线上学习系统

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性:…

单片机郭天祥(02)

1:解决keil5软件的乱码问题,修改编码为UTF-8 2:打开keil5使用debug对编写好的程序进行调试 给程序打上断点 使用仿真芯片 更改设备管理器相关设置 接通电源后点击debug连接到51单片机 使用stc-isp获取延时函数 将延时函数添加进入创建好的…

17-spring aop调用过程概述

文章目录 1.源码2. debug过程 1.源码 public class TestAop {public static void main(String[] args) throws Exception {saveGeneratedCGlibProxyFiles(System.getProperty("user.dir") "/proxy");ApplicationContext ac new ClassPathXmlApplication…

在JavaScript中,如何创建一个数组或对象?

在JavaScript中,可以使用以下方式创建数组和对象: 一:创建数组(Array): 1:使用数组字面量(Array Literal)语法,使用方括号 [] 包裹元素,并用逗号分隔: let array1 = []; // 空数组 let array2 = [1, 2, 3]; // 包含三个数字的数组 let array3 = [apple, banana,…

Nautilus Chain 与 Coin98 生态达成合作,加速 Zebec 生态亚洲战略进程

目前,行业内首个模块化 Layer3 架构公链 Nautilus Chain 已经上线主网,揭示了模块化区块链领域迎来了全新的进程。在主网上线后,Nautilus Chain 将扮演 Zebec 生态中最重要的底层设施角色,并将为 Zebec APP 以及 Zebec Payroll 规…

ESP32C3 LuatOS TM1650②动态显示累加整数

--注意:因使用了sys.wait()所有api需要在协程中使用 -- 用法实例 PROJECT "ESP32C3_TM1650" VERSION "1.0.0" _G.sys require("sys") local tm1650 require "tm1650"-- 拆分整数,并把最低位数存放在数组最大索引处 loc…

用Nginx搭建一个具备缓存功能的反向代理服务

在同一台服务器上,使用nginx提供服务,然后使用openresty提供反向代理服务。 参考《Ubuntu 20.04使用源码安装nginx 1.14.0》安装nginx。 参考《用Nginx搭建一个可用的静态资源Web服务器》搭建静态资源Web服务器,但是/nginx/conf/nginx.conf里…

Uniapp软件库源码 全新带勋章功能(包含前后端源码)

Uniapp软件库全新带勋章功能,搭建好后台 在前端找到 util 这个文件 把两个js文件上面的填上自己的域名, 电脑需要下载:HBuilderX 登录账号 没有账号就注册账号,然后上传文件,打包选择 “发行” 可以打包app h5等等。…

异常数据检测 | Python基于Hampel的离群点检测

文章目录 文章概述模型描述源码分享文章概述 在时间序列数据分析领域,识别和处理异常点是至关重要的任务。异常点或离群点是明显偏离预期模式的数据点,可能表明存在错误、欺诈或有价值的见解。 应对这一挑战的一种有效技术是汉普尔过滤器(Hampel Filter)。 模型描述 汉…

spark获取hadoop服务token

spark 作业一直卡在accepted 问题现象问题排查1.查看yarn app日志2.问题分析与原因 问题现象 通过yarn-cluster模式提交spark作业,客户端日志一直卡在submit app,没有运行 问题排查 1.查看yarn app日志 appid已生成,通过yarn查看app状态为…

Note——torch.size() umr_maximum() array.max() itertools.product()

torch.size Problem TypeError: ‘torch.Size’ object is not callable Reason Analysis torch.Size函数不可调用 因为torch只可以.size() 或 shape Solution 将y.shape()替换为y.size() 或 y.shape ytorch.normal(0,0.01,y.size())2 return umr_maximum(a, axis, None…

uniapp接入萤石微信小程序插件

萤石官方提供了一些适用于uniapp / 小程序的方案 如 小程序半屏 hls rtmp 等 都TM有坑 文档写的依托答辩 本文参考了uniapp小程序插件 以及 萤石微信小程序插件接入文档 效果如下 1. 插件申请 登录您的小程序微信公众平台,点击左侧菜单栏,进入设置页…

盒式交换机堆叠配置

目录 1.配置环形拓扑堆叠 2.设备组建堆叠 3.设备组件堆叠 堆叠 istack,是指将多台支持堆叠特性的交换机设备组合在一起,从逻辑上组合成一台交换设备。如图所示,SwitchA与 SwitchB 通过堆叠线缆连接后组成堆叠 istack,对于上游和…

百度地图API:JavaScript开源库几何运算判断点是否在多边形内(电子围栏)

百度地图JavaScript开源库,是一套基于百度地图API二次开发的开源的代码库。目前提供多个lib库,帮助开发者快速实现在地图上添加Marker、自定义信息窗口、标注相关开发、区域限制设置、几何运算、实时交通、检索与公交驾车查询、鼠标绘制工具等功能。 判…