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

目录

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

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

定义

依赖倒置原则(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.rhkb.cn/news/166609.html

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

相关文章

【广州华锐互动】全屋智能家电VR虚拟仿真演示系统

在过去的几年中,智能家居的概念已经逐渐进入人们的生活。然而,它的真正潜力和最终形态可能还未被完全发掘。一种新兴的技术,虚拟现实(VR),为我们提供了一种全新的方式来理解和体验智能家居。VR公司广州华锐…

FFT64点傅里叶变换verilog蝶形运算,代码和视频

名称:FFT64点verilog傅里叶变换 软件:Quartus 语言:Verilog 代码功能: 使用verilog代码实现64点FFT变换,使用蝶形运算实现傅里叶变换 演示视频:http://www.hdlcode.com/index.php?mhome&cView&…

STM32F4X之GPIO

一、GPIO概述 主控芯片信息如下: 主频:168MHZ内核:ARM-M4FLASH:1MSRAM:192KB引脚:100GPIO:82电压:1.8~3.6V 1.1GPIO概念及其作用 GPIO概念:通用输入输出(General Purpose Input Output),主要作用…

How to add a jar to a project in eclipse?

Project -> Properties -> Java Build Path -> Libraries -> Add External JARs

前端多媒体处理工具——ffmpeg的使用

写在前面 在前端领域,FFmpeg 是一个非常有用的工具,它提供了多种媒体格式的封装和解封装,包括多种音视频编码、多种协议的流媒体、多种色彩格式转换、多种采样率转换、多种码率切换等。可以在多种操作系统安装使用。 安装 下载FFmpeg 在网…

免费Scrum管理工具-Leangoo领歌

Leangoo领歌是一款永久免费的专业的敏捷开发管理工具,提供端到端敏捷研发管理解决方案,涵盖敏捷需求管理、任务协同、进展跟踪、统计度量等。 
 Leangoo领歌上手快、实施成本低,可帮助企业快速落地敏捷,提质增效、缩短周期、加速…

vue PWA serviceWorker 有新内容时,如何自动刷新内容

vue PWA serviceWorker 有新内容时,如何自动刷新内容 一、问题描述 vue 自带的 pwa 插件可以很方便管理 serviceWorker 的使用,但会有一个问题。 ServiceWorker 的运行机制是这样的: 后台检测到新版本新版 ServiceWorker 下载并安装安装完…

【MySQL】8.0新特性、窗口函数和公用表表达式

文章目录 1. 新增特性2. 移除旧特性2.1 优点2.2 缺点 3. 新特性1:窗口函数3.1 使用窗口函数前后对比3.2 窗口函数分类3.3 语法结构3.4 分类讲解3.4.1 序号函数3.4.1.1 ROW_NUMBER()函数3.4.1.2 RANK()函数3.4.1.3 DENSE_RANK()函数 3.4.2 分布函数3.4.2.1 PERCENT_R…

Qt使用QListWidget实现自定义Item效果

Q:如何在Qt库的基础上,实现自定义控件呢? A:根据官方文档回答,就是继承需实现的控件,然后实现自定义功能。 以下是实现QListWidget控件的自定义item。 先看下最终效果是如何: listItem 主界面U…

linux安装新版本git2、配置github-ssh。(centos、aws)

一、安装Git 1、yum默认版本git #1.安装git sudo yum install git -y #2.确认Git已经安装成功 git --version如果要安装较新版本,可以安装一个repo ,但是我这第一次尝试失败了,执行完提示找不到git2u,ius repo也连不上。而且每次…

HTML图像标签

html文件&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>图像标签学习</title> </head> <body> <img src"../resources/image/01.jpg" alt"小狗图…

通过WinSCP实现Windows给Ubuntu(Linux)虚拟机传输数据

要实现传输有几个准备工作需要做 1.在虚拟机运行工具&#xff08;VMware或者其他&#xff09;中设置网络&#xff08;或者网络适配器&#xff09;为桥接模式&#xff08;之前是NAT模式&#xff09; 2.使用ifconfig命令查看虚拟机的网络地址 3.确定虚拟机中安装了ssh 安装 sudo…

YOLOv5算法改进(17)— 手把手教你去更换损失函数(IoU/GIoU/DIoU/CIoU/EIoU/AlphaIoU/SIoU)

前言:Hello大家好,我是小哥谈。损失函数(loss function)是机器学习中用来衡量模型预测值与真实值之间差异的函数。它用于度量模型在训练过程中的性能,以便优化模型参数。在训练过程中,损失函数会根据模型的预测结果和真实标签计算出一个标量值,代表了模型预测的错误程度…

FreeRTOS_队列

目录 1. 队列简介 1.1 数据存储 1.2 多任务访问 1.3 出队阻塞 1.4 入队阻塞 1.5 队列操作过程 1.5.1 创建队列 1.5.2 向队列发送第一个消息 1.5.3 向队列发送第二个消息 1.5.4 从队列中读取消息 2. 队列结构体 3. 队列创建 3.1 函数原型 3.1.1 函数 xQueueCreate…

分类预测 | MATLAB实现SSA-CNN-LSTM麻雀算法优化卷积长短期记忆神经网络数据分类预测

分类预测 | MATLAB实现SSA-CNN-LSTM麻雀算法优化卷积长短期记忆神经网络数据分类预测 目录 分类预测 | MATLAB实现SSA-CNN-LSTM麻雀算法优化卷积长短期记忆神经网络数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.MATLAB实现SSA-CNN-LSTM数据分类预测&…

Python 创建或读取 Excel 文件

Excel是一种常用的电子表格软件&#xff0c;广泛应用于金融、商业和教育等领域。它提供了强大的数据处理和分析功能&#xff0c;可进行各种计算和公式运算&#xff0c;并能创建各种类型的图表和可视化数据。Excel的灵活性使其成为处理和管理数据的重要工具。本文将介绍如何使用…

24---WPF缓存

一、什么是缓存&#xff1a; 1.缓存指的是将需要频繁访问的网络内容存放在离用户较近、访问速度更快的系统中&#xff0c;以提高内容访问速度的一种技术。缓存服务器就是存放频繁访问内容的服务器。 2.缓存就是一个临时存放区域--离用户比较近 二、作用--意义---如果系统出现故…

解决IDEA中SpringBoot项目创建多个子模块时配置文件小绿叶图标异常问题

在新建子模块下创建配置文件&#xff1a; 在子模块gateway中新建的配置文件,正常情况下配置文件左侧是小树叶标识&#xff0c;而这次新建application-dev.yml是个小树叶标识&#xff0c;bootstrap.yml是个方框。 看其他方案都是在project structure中设置&#xff0c;但未显示…

UWB安全数据通讯STS-加密、身份认证

DW3000系列才能支持UWB安全数据通讯&#xff0c;DW1000不支持 IEEE 802.15.4a没有数据通讯安全保护机制&#xff0c;IEEE 802.15.4z中指定的扩展得到增强&#xff08;在PHY/RF级别&#xff09;&#xff1a;增添了一个重要特性“扰频时间戳序列&#xff08;STS&#xff09;”&a…

【TensorFlow1.X】系列学习笔记【基础一】

【TensorFlow1.X】系列学习笔记【基础一】 大量经典论文的算法均采用 TF 1.x 实现, 为了阅读方便, 同时加深对实现细节的理解, 需要 TF 1.x 的知识 文章目录 【TensorFlow1.X】系列学习笔记【基础一】前言线性回归非线性回归逻辑回归总结 前言 本篇博主将用最简洁的代码由浅入…