【23种设计模式】依赖倒置原则
个人主页:金鳞踏雨
个人简介:大家好,我是金鳞,一个初出茅庐的Java小白
目前状况:22届普通本科毕业生,几经波折了,现在任职于一家国内大型知名日化公司,从事Java开发工作
我的博客:这里是CSDN,是我学习技术,总结知识的地方。希望和各位大佬交流,共同进步 ~
要依赖于抽象而不是具体实现。
依赖倒置的目的是,低层模块可以随时替换,以提高代码的可扩展性。
一、原理
要依赖于抽象而不是具体实现。遵循这个原则可以使系统的设计更加灵活、可扩展和可维护。
- 高层模块不应该依赖于低层模块,它们都应该依赖于抽象。
- 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
倒置在这里的确是指"反过来"的意思。在依赖倒置原则中,我们需要改变依赖关系的方向,使得高层模块和低层模块都依赖于抽象,而不是高层模块直接依赖于低层模块。这样一来,依赖关系就从直接依赖具体实现"反过来"依赖抽象了。
这种"倒置"的依赖关系使得系统的耦合度降低,提高了系统的可维护性和可扩展性。因为,当低层模块的具体实现发生变化时,只要不改变抽象,高层模块就不需要进行调整。所以这个原则叫做依赖倒置原则。
二、如何理解抽象
当我们在讨论依赖倒置原则中的抽象时,绝对不能仅仅把他理解为一个接口。抽象的目的是将关注点从具体实现转移到概念和行为,使得我们在设计和编写代码时能够更加关注问题的本质。通过使用抽象,我们可以创建更加灵活、可扩展和可维护的系统。
事实上抽象是一个很广泛的概念,它可以包括接口、抽象类以及由大量接口,抽象类和实现组成的更高层次的模块。通过将系统分解为更小的、可复用的组件,我们可以实现更高层次的抽象。这些组件可以独立地进行替换和扩展,从而使整个系统更加灵活。
1. 接口
接口是 Java 中实现抽象的一种常见方式。接口定义了一组方法签名,表示实现该接口的类应具备哪些行为。接口本身并不包含具体实现,所以它强调了行为的抽象。
假设我们正在开发一个在线购物系统,其中有一个订单处理模块。订单处理模块需要与不同的支付服务提供商(如 PayPal、Stripe 等)进行交互。如果我们直接依赖于支付服务提供商的具体实现,那么在更换支付服务提供商或添加新的支付服务提供商时,我们可能需要对订单处理模块进行大量修改。为了避免这种情况,我们应该依赖于接口而不是具体实现。
首先,我们定义一个支付服务接口
public interface PaymentService {boolean processPayment(Order order);
}
然后,为每个支付服务提供商实现该接口
public class PayPalPaymentService implements PaymentService {@Overridepublic boolean processPayment(Order order) {// 实现 PayPal 支付逻辑}
}public class StripePaymentService implements PaymentService {@Overridepublic boolean processPayment(Order order) {// 实现 Stripe 支付逻辑}
}
现在,我们可以在订单处理模块中依赖 PaymentService 接口,而不是具体的实现:
public class OrderProcessor {private PaymentService paymentService;public OrderProcessor(PaymentService paymentService) {this.paymentService = paymentService;}public void processOrder(Order order) {// 其他订单处理逻辑...boolean paymentResult = paymentService.processPayment(order);// 根据 paymentResult 处理支付结果}
}
通过这种方式,当我们需要更换支付服务提供商或添加新的支付服务提供商时,只需要提供一个新的实现类,而不需要修改 OrderProcessor 类。我们可以在运行时通过构造函数注入不同的支付服务实现,使得系统更加灵活和可扩展。
2. 抽象类
抽象类是另一种实现抽象的方式。与接口类似,抽象类也可以定义抽象方法,表示子类应该具备哪些行为。不过抽象类还可以包含部分具体实现,这使得它们比接口更加灵活。
abstract class Shape {abstract double getArea();void displayArea() {System.out.println("面积为: " + getArea());}
}class Circle extends Shape {private final double radius;Circle(double radius) {this.radius = radius;}@Overridedouble getArea() {return Math.PI * Math.pow(radius, 2);}
}class Square extends Shape {private final double side;Square(double side) {this.side = side;}@Overridedouble getArea() {return Math.pow(side, 2);}
}
在这个示例中,我们定义了一个抽象类 Shape,它具有一个抽象方法 getArea,用于计算形状的面积。同时,它还包含了一个具体方法 displayArea,用于打印面积。
Circle 和 Square 类继承了 Shape,分别实现了 getArea 方法。在其他类中我们可以依赖抽象Shape而非 Square和Circle。
三、如何理解高层模块和底层模块
所谓高层模块和低层模块的划分,简单来说就是,在调用链上,调用者属于高层,被调用者属于低层。在平时的业务代码开发中,高层模块依赖底层模块是没有任何问题的。实际上,这条原则主要还是用来指导框架层面的设计,跟前面讲到的控制反转类似。
用 Tomcat 这个 Servlet 容器作为例子来解释一下。从业务代码上讲,举一个简单的例子就是controller要依赖service的接口而不是实现,service实现要依赖dao层的接口而不是实现,调用者要依赖被调用者的接口而不是实现。
以一个简单的音频播放器为例,高层模块 AudioPlayer 负责播放音频,而音频文件的解码由低层模块 Decoder 实现。为了遵循依赖倒置原则,我们可以引入一个抽象的解码器接口:
interface AudioDecoder {AudioData decode(String filePath);
}class AudioPlayer {private final AudioDecoder decoder;public AudioPlayer(AudioDecoder decoder) {this.decoder = decoder;}public void play(String filePath) {AudioData audioData = decoder.decode(filePath);// 使用解码后的音频数据进行播放}
}class MP3Decoder implements AudioDecoder {@Overridepublic AudioData decode(String filePath) {// 实现 MP3 文件解码}
}
在这个例子中,我们将高层模块 AudioPlayer 和低层模块 MP3Decoder 解耦,使它们都依赖于抽象接口 AudioDecoder。这样,我们可以根据需要轻松地更换音频解码器(例如,支持不同的音频格式),而不影响音频播放器的逻辑。为了支持新的音频格式,我们只需要实现新的解码器类,并将其传递给 AudioPlayer。
假设我们现在要支持 WAV 格式的音频文件,我们可以创建一个实现 AudioDecoder 接口的新类:
class WAVDecoder implements AudioDecoder {@Overridepublic AudioData decode(String filePath) {// 实现 WAV 文件解码}
}
然后,在创建 AudioPlayer 对象时,我们可以根据需要选择使用 MP3Decoder 或 WAVDecoder:
public static void main(String[] args) {AudioDecoder mp3Decoder = new MP3Decoder();AudioPlayer mp3Player = new AudioPlayer(mp3Decoder);mp3Player.play("example.mp3");AudioDecoder wavDecoder = new WAVDecoder();AudioPlayer wavPlayer = new AudioPlayer(wavDecoder);wavPlayer.play("example.wav");
}
通过遵循依赖倒置原则,我们将高层模块 AudioPlayer 与低层模块 MP3Decoder 和 WAVDecoder 解耦,使它们都依赖于抽象接口 AudioDecoder。这样的设计使得我们可以轻松地为音频播放器添加新的音频格式支持,同时保持整个系统的灵活性和可维护性。
Tomcat
Tomcat 是运行 Java Web 应用程序的容器。我们编写的 Web 应用程序代码只需要部署在 Tomcat 容器下,便可以被 Tomcat 容器调用执行。
按照之前的划分原则,Tomcat 就是高层模块,我们编写的 Web 应用程序代码就是低层模块。
Tomcat 和应用程序代码之间并没有直接的依赖关系,两者都依赖同一个“抽象”,也就是 Sevlet 规范。Servlet 规范不依赖具体的 Tomcat 容器和应用程序的实现细节,而 Tomcat 容器和应用程序依赖 Servlet 规范。这样做的好处就是tomcat中可以运行任何实现了servlet规范的应用程序,同时我们编写的servlet实现(web)工程也可以运行在不同的web服务器中。
四、IOC容器
依赖倒置的目的是,低层模块可以随时替换,以提高代码的可扩展性。
其实我们学过spring的同学应该都清楚,在spring中实现这个很简单的,我们只需要向容器中注入特定的bean就能切换具体实现。同时我们在编写日常代码时,有意无意的都会遵循设计原则。
控制反转是一种软件设计原则,它将传统的控制流程颠倒过来,将控制权交给一个中心化的容器或框架。
依赖注入是指不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。
通过控制翻转和依赖注入结合,我们只要保证依赖抽象而不是实现,就能很轻松的替换实现。如给容器注入一个MySQL的数据,则所有依赖数据源的部分会自动使用MySQL,如果想替换数据源则仅仅需要给容器注入一个新的数据源就好了,不需要修改一行代码。
文章到这里就结束了,如果有什么疑问的地方,可以在评论区指出~
希望能和大佬们一起努力,诸君顶峰相见
再次感谢各位小伙伴儿们的支持!!!
相关文章:

【23种设计模式】依赖倒置原则
个人主页:金鳞踏雨 个人简介:大家好,我是金鳞,一个初出茅庐的Java小白 目前状况:22届普通本科毕业生,几经波折了,现在任职于一家国内大型知名日化公司,从事Java开发工作 我的博客&am…...

C++ 结构简介
假设要存储有关篮球运动员的信息,则可能需要存储他(她)的姓名、工资、身高、体重、平均得 分、命中率、助攻次数等。希望有一种数据格式可以将所有这些信息仔储在一个单元中。数组不能完成 这项任务,因为虽然数组可以存储多个元素,但所有元素的类型必须相同。也就是说,一个数组…...

element的tabs组件使用问题解决
1.去除el-tabs组件自带的键盘切换功能 今天在使用element的tabs组件时,发现这个tab组件自带了按键盘左右方向,切换tab的功能,可以通过使用keydown.native.capture.stop去除该事件 <el-tabs v-model"editableTabsValue"type&qu…...

python实验1 猜数字游戏
实验0:猜数字游戏 1. 猜数字游戏 版本12. 猜数字游戏 版本23. 猜数字游戏 版本34. 猜数字游戏 版本4 1. 猜数字游戏 版本1 题目猜数字游戏。在程序中预设一个0-9之间的整数, 让用户通过键盘输入所猜的数, 如果大于预设的数,显示“你猜的数字大于正确答案…...

docker 中给命令起别名
docker 的有些命令特别复杂,我们可以给它设置别名简化输入,就不用每次都输入那么多了!!! 1. 进入 .bashrc 中修改配置( .bashrc 是root下的隐藏文件) cd /rootvim .bashrc2. 在 .bashrc 中加入…...

PHP的yaf框架自带插件
Yaf 框架的插件方法触发流程遵循一定的顺序,具体流程如下 Bootstrap 类的 _initPlugin 方法:在 Yaf 应用程序启动时,首先会执行 Bootstrap 类的 _initPlugin 方法。在这个方法中,你可以注册各种插件。例如: phpCopy …...

SpringCloud Alibaba【三】Gateway
Gateway配置与使用 前言新建gateway子项目pom.xml配置文件启动类访问接口方式 测试拓展 前言 在工作中遇到一种情况,一个父项目中有两个子项目。实际使用时,需要外网可以访问,宝信软件只能将一个端口号发布在外网上,所以需要运用…...

Azure - 机器学习实战:快速训练、部署模型
本文将指导你探索 Azure 机器学习服务的主要功能。在这里,你将学习如何创建、注册并发布模型。此教程旨在让你深入了解 Azure 机器学习的基础知识和常用操作。 关注TechLead,分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验…...

C语言十进制转其它进制
短除法介绍 短除法: 主要功能为将十进制数据转为其它进制的数据,假设我们要转换为 X 进制,那么具体的流程如下: 十进制数字不断除以 X,直到商为 0 记录每次计算得到的余数 将余数倒序输出,即为对应的 X 进…...

网络建设 之 React数据管理
React作为一个用于构建用户界面的JavaScript库,很多人认为React仅仅只是一个UI 库,而不是一个前端框架,因为它在数据管理上是缺失的。在做一个小项目的时候,维护的数据量不多,管理/维护数据用useState/useRef就足够了&…...

如何隐藏woocommerce 后台header,woocommerce-layout__header
如何隐藏woocommerce 后台header,woocommerce-layout__header WooCommerce |Products Store Activity| Inbox| Orders| Stock| Reviews| Notices| breadcrumbs 在 functions.php 里添加如下代码即可: // Disable WooCommerce Header in WordPress Admi…...

通俗易懂的理解 解耦 概念
解耦(Decoupling)是计算机科学和软件工程中的一个概念,指的是降低系统中不同部分之间的依赖性,使系统的各个组件能够相对独立地进行开发、维护和演化。解耦的主要目标是减少组件之间的紧密耦合,以提高系统的灵活性、可…...

全志A40i android7.1 增加Vlan功能
一,VLAN基础知识 1.VLAN的定义: VLAN(Virtual Local Area Network)即虚拟局域网,是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。VLAN内的主机间可以直接通信,而VLAN间不能直接通信,从…...

NAT技术与代理服务器
目录 一、NAT与NAPT技术 1.NAT技术 2.NAPT技术 (1)四元组的唯一性 (2)数据的传输过程 (3)NAPT的缺陷 二、代理服务器 1.正向代理和反向代理 2.代理服务器的应用 (1)游戏加…...

关于报错java.util.ConcurrentModificationException: null的源码分析和解决
一般有这种问题,方法中至少会有List或者Map下的至少两个子类,有可能参数类型相同,也有可能不同都有可能触发这个问题!其主要原因是使用了ArrayList进行删除操作或者使用iterator遍历集合的同时对集合进行修改都有可能会出现这个问题 ArrayList属于List下的子类 需要区分的是Li…...

使用koa搭建服务器(一)
最近有个需求需要使用到koa搭建服务器并编写接口对数据库进行增删改查,因此写一篇博客记录这段时间的收获。 一、新建koa项目 (一)安装koa及其相关依赖 npm i koa npm i koa-router// 中间件,用于匹配路由 npm i koa-bodyparse…...

echarts的柱状图的重叠和堆叠实现两个柱体的显示和之前的差值显示
效果图 主要思路 准备三个柱体(原计划,实际进度,差值) 原计划和实际进度设置成重叠 {barWidth: 20,// yAxisIndex: 1,z: 1,name: 原计划,type: bar,stack: ab,emphasis: { // 点击柱体其他柱体颜色会变浅disabled: true},label…...

泛积木-低代码 使用攻略
文档首发于 泛积木-低代码 使用攻略 我们以大纲的方式(总体把握)讲述如何高效、便捷使用 泛积木-低代码。 权限 首先说下权限,在 系统设置 / 权限设置 菜单内,我们可以新增调整项目内的权限,默认拥有管理员和成员两…...

红队专题-从零开始VC++C/S远程控制软件RAT-MFC-远控介绍及界面编写
红队专题 招募六边形战士队员[1]远控介绍及界面编写1.远程控制软件演示及教程简要说明主程序可执行程序 服务端生成器主机上线服务端程序 和 服务文件管理CMD进程服务自启动主程序主对话框操作菜单列表框配置信息 多线程操作非模式对话框 2.环境:3.界面编程新建项目…...

机器学习(五)如何理解机器学习三要素
1.8如何理解机器学习三要素 统计学习模型策略算法 模型:规律yaxb 策略:什么样的模型是好的模型?损失函数 算法:如何高效找到最优参数,模型中的参数a和b 1.8.1模型 机器学习中,首先要考虑学习什么样的…...

【计算机视觉】3D视觉
文章目录 一、基本问题二、三个坐标系 X w \boldsymbol{X}_w Xw到 X c \boldsymbol{X}_c Xc的转换 X c \boldsymbol{X}_c Xc到 x i \boldsymbol{x}_i xi的转换投影矩阵尺度模糊问题 三、相机标定四、立体视觉 我的《计算机视觉》系列参考UC Berkeley的CS180课程&#x…...

策略路由和路由策略
目录 策略路由 路由策略 策略路由和路由策略 策略路由 Step1:配置ACL,匹配流量 acl number 2010 rule 10 permit source 192.168.10.0 0.0.0.255 acl number 2020 rule 10 permit source 192.168.20.0 0.0.0.255 Step2:流分类traffic classifier jiaoxue //匹配…...

[动态规划] (一) LeetCode 1137.第N个泰波那契数
[动态规划] (一) LeetCode 1137.第N个泰波那契数 文章目录 [动态规划] (一) LeetCode 1137.第N个泰波那契数题目解析解题思路状态表示状态转移方程初始化和填表顺序返回值 代码实现总结空间优化代码实现 总结 1137. 第 N 个泰波那契数 题目解析 解题思路 状态表示 (1) 题目要…...

SystemVerilog语法中,在Class中引用层次化信号
在class中可以像在verilog中一样,直接在class中引用层次化信号。示例如下: 1.DUT模块,文件名为top.v。 module top (input clk ,input rst_n ,//总线信号 input wr_n ,input rd_n ,input cs0_n ,input cs7_n …...

磁盘的结构(磁道,扇区,盘面,柱面,物理地址)
目录 1.磁盘、磁道、扇区的概念1.磁盘2.磁道3.扇区 2.如何在磁盘中读/写数据3.盘面、柱面的概念4.磁盘的物理地址1.根据地址读取一个“块” 5.磁盘的分类1.活动头磁道2.固定头磁盘3.根据盘片是否可更换 1.磁盘、磁道、扇区的概念 1.磁盘 磁盘的表面由一些磁性物质组成…...

uni-app集成uni-simple-router,报错:Uncaught ReferenceError: ROUTES is not defined
参考连接:GitHub - SilurianYang/uni-read-pages: read pages.json file to generate the routes table 作用:配置 vue.config.js 通过 webpack注入全局变量 问题:缺少Webpack 配置环境 方法: 项目根目录下打开终端,…...

几个常用的nosql数据库的操作方式
dynamoDB 键 partition key:分区键 定义:分区键是用于分布数据存储的主键,每个项(Item)在表中都必须有一个唯一的分区键值。 特点: 唯一性:每个分区键值在表中必须是唯一的,这是因为…...

如何使用 nvm-windows 这个工具来管理你电脑上的Node.js版本
nvm-windows 是一个用于管理在 Windows 上安装的多个 Node.js 版本的工具。以下是安装和使用 nvm-windows 的步骤: 第1步:下载 nvm-windows 访问 nvm-windows 的 GitHub发布页面.下载最新版本的 nvm-setup.zip 文件。 第2步:安装 nvm-wind…...

公司电脑禁用U盘的方法
公司电脑禁用U盘的方法 安企神U盘管理系统下载使用 在这个复杂的数据时代,保护公司数据的安全性至关重要。其中,防止未经授权的数据泄露是其中的一个关键环节。U盘作为一种常用的数据传输工具,也成为了潜在的安全风险。因此,公司…...

Elasticsearch 7.X版本常用语法语句
文章目录 监控相关 API查看健康状况查看所有节点查看所有节点详细信息查看主节点查看所有索引查看所有分片 索引管理创建索引查看索引查看索引字段类型修改索引字段删除索引别名给索引添加别名查询某个索引下的别名给索引更换别名给索引解绑别名一个别名绑定多个索引查询index_…...