当前位置: 首页 > news >正文

【23种设计模式】依赖倒置原则

个人主页:金鳞踏雨

个人简介:大家好,我是金鳞,一个初出茅庐的Java小白

目前状况:22届普通本科毕业生,几经波折了,现在任职于一家国内大型知名日化公司,从事Java开发工作

我的博客:这里是CSDN,是我学习技术,总结知识的地方。希望和各位大佬交流,共同进步 ~

要依赖于抽象而不是具体实现

依赖倒置的目的是,低层模块可以随时替换,以提高代码的可扩展性。

一、原理

要依赖于抽象而不是具体实现。遵循这个原则可以使系统的设计更加灵活、可扩展和可维护

  1. 高层模块不应该依赖于低层模块,它们都应该依赖于抽象。
  2. 抽象不应该依赖于具体实现,具体实现应该依赖于抽象。

倒置在这里的确是指"反过来"的意思。在依赖倒置原则中,我们需要改变依赖关系的方向,使得高层模块和低层模块都依赖于抽象,而不是高层模块直接依赖于低层模块。这样一来,依赖关系就从直接依赖具体实现"反过来"依赖抽象了

这种"倒置"的依赖关系使得系统的耦合度降低,提高了系统的可维护性和可扩展性。因为,低层模块的具体实现发生变化时,只要不改变抽象,高层模块就不需要进行调整所以这个原则叫做依赖倒置原则。

二、如何理解抽象

当我们在讨论依赖倒置原则中的抽象时,绝对不能仅仅把他理解为一个接口。抽象的目的是将关注点从具体实现转移到概念和行为,使得我们在设计和编写代码时能够更加关注问题的本质。通过使用抽象,我们可以创建更加灵活、可扩展和可维护的系统。

事实上抽象是一个很广泛的概念,它可以包括接口、抽象类以及由大量接口,抽象类和实现组成的更高层次的模块。通过将系统分解为更小的、可复用的组件,我们可以实现更高层次的抽象。这些组件可以独立地进行替换和扩展,从而使整个系统更加灵活。

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组件时&#xff0c;发现这个tab组件自带了按键盘左右方向&#xff0c;切换tab的功能&#xff0c;可以通过使用keydown.native.capture.stop去除该事件 <el-tabs v-model"editableTabsValue"type&qu…...

python实验1 猜数字游戏

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

docker 中给命令起别名

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

PHP的yaf框架自带插件

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

SpringCloud Alibaba【三】Gateway

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

Azure - 机器学习实战:快速训练、部署模型

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

C语言十进制转其它进制

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

网络建设 之 React数据管理

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

如何隐藏woocommerce 后台header,woocommerce-layout__header

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

通俗易懂的理解 解耦 概念

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

全志A40i android7.1 增加Vlan功能

一&#xff0c;VLAN基础知识 1.VLAN的定义&#xff1a; VLAN&#xff08;Virtual Local Area Network&#xff09;即虚拟局域网&#xff0c;是将一个物理的LAN在逻辑上划分成多个广播域的通信技术。VLAN内的主机间可以直接通信&#xff0c;而VLAN间不能直接通信&#xff0c;从…...

NAT技术与代理服务器

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

关于报错java.util.ConcurrentModificationException: null的源码分析和解决

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

使用koa搭建服务器(一)

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

echarts的柱状图的重叠和堆叠实现两个柱体的显示和之前的差值显示

效果图 主要思路 准备三个柱体&#xff08;原计划&#xff0c;实际进度&#xff0c;差值&#xff09; 原计划和实际进度设置成重叠 {barWidth: 20,// yAxisIndex: 1,z: 1,name: 原计划,type: bar,stack: ab,emphasis: { // 点击柱体其他柱体颜色会变浅disabled: true},label…...

泛积木-低代码 使用攻略

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

红队专题-从零开始VC++C/S远程控制软件RAT-MFC-远控介绍及界面编写

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

机器学习(五)如何理解机器学习三要素

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

基于算法竞赛的c++编程(28)结构体的进阶应用

结构体的嵌套与复杂数据组织 在C中&#xff0c;结构体可以嵌套使用&#xff0c;形成更复杂的数据结构。例如&#xff0c;可以通过嵌套结构体描述多层级数据关系&#xff1a; struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...

电脑插入多块移动硬盘后经常出现卡顿和蓝屏

当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时&#xff0c;可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案&#xff1a; 1. 检查电源供电问题 问题原因&#xff1a;多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

聊一聊接口测试的意义有哪些?

目录 一、隔离性 & 早期测试 二、保障系统集成质量 三、验证业务逻辑的核心层 四、提升测试效率与覆盖度 五、系统稳定性的守护者 六、驱动团队协作与契约管理 七、性能与扩展性的前置评估 八、持续交付的核心支撑 接口测试的意义可以从四个维度展开&#xff0c;首…...

SQL慢可能是触发了ring buffer

简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...

现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?

现有的 Redis 分布式锁库&#xff08;如 Redisson&#xff09;相比于开发者自己基于 Redis 命令&#xff08;如 SETNX, EXPIRE, DEL&#xff09;手动实现分布式锁&#xff0c;提供了巨大的便利性和健壮性。主要体现在以下几个方面&#xff1a; 原子性保证 (Atomicity)&#xff…...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing

Muffin 论文 现有方法 CRADLE 和 LEMON&#xff0c;依赖模型推理阶段输出进行差分测试&#xff0c;但在训练阶段是不可行的&#xff0c;因为训练阶段直到最后才有固定输出&#xff0c;中间过程是不断变化的。API 库覆盖低&#xff0c;因为各个 API 都是在各种具体场景下使用。…...