SOLID-开闭原则
单一职责原则:https://blog.csdn.net/dmk877/article/details/143447010
在前面我们学习了单一职责原则,今天来一起学习一下SOLID原则中的开闭原则(Open-Closed Principle, OCP)
通过本篇博客你将学到到以下内容
①什么是开闭原则
②如何实现开闭原则
③两个开闭原则的案例
一、什么是开闭原则
首先我们来看下开闭原则的定义:
Software entities like classes,modules and functions should be open for extension but closed for modifications.
软件实体如类、模块和函数应该对扩展开放,对修改关闭。
怎么理解这句话呢?这句话最重要的就是"对扩展开放,对修改关闭"
- 对扩展开放:当有新的需求或变化时,可以对现有代码进行扩展,以适应新的需求
- 对修改关闭:需求一旦开发完成,就可以独立完成其工作,而不要对已有代码做修改
二、如何实现开闭原则
一般用来提高扩展性的方法有:多态、依赖注入、面向抽象而非面向具体编程,怎么利用多态、依赖注入、面向抽象而非具体编程,来实现“对扩展开放、对修改关闭”呢?接下来我举两个例子,相信通过这两个例子你对开闭原则的理解会更加深入。
2.1 举例一
什么依赖注入,什么是面向抽象而非具体的编程
// 将图形进行抽象
public interface Shape {//..}
// 圆形
public class Cirlce implements Shape {//..}
// 三角形
public class Triangle implements Shape {//..}
// 长方形
public class Rectangle implements Shape {//..}public class Demo {private Shape shape; // 基于接口而非实现的编程// 依赖注入public Demo(Shape shape) {this.shape = shape;}
}
以上这段伪代码就是基于接口而非具体编程,将图形共同的属性和方法抽取到Shape接口中,然后针对不同的图形会分别实现接口中的功能。那么问题来了,为什么要这么做呢?来一个完整的例子,通过这个例子你就会明白为什么要这么做
2.2 举例二 电商支付系统
假如我们在开发电商平台的支付系统,当前支付的方式有Alipay、WechatPay,这个支付系统如何设计呢?首先可以定义个PayManager来统一管理支付。代码如下
public class PayManager {public void pay(int payMode) {if (payMode == 1) {aliPay();} else if (payMode == 2) {wechatPay();}}private void aliPay() {System.out.println("调用 alipay 接口");}private void wechatPay() {System.out.println("调用 wechatpay 接口");}
}
可以看到在PayManager的pay方法里根据传递的参数来判断应该使用哪种支付方式,貌似没啥问题,但是随着业务的发展需要增加银行卡支付方式BankCardPay,应该怎么处理呢?需要修改两个地方
- pay方法里增加一个if分支
- 写一个bankcardPay方法
代码如下
public class PayManager {public void pay(int payMode) {if (payMode == 1) {aliPay();} else if (payMode == 2) {wechatPay();} else if (payMode == 3) {// 修改点一:增加一个if分支bankcardPay();}}private void aliPay() {System.out.println("调用 alipay 接口");}private void wechatPay() {System.out.println("调用 wechatpay 接口");}// 修改点二:增加一个bankcardPay方法private void bankcardPay() {System.out.println("调用 bankcardpay 接口");}
}
有没有发现一个问题,PayManager里的pay方法进行了修改,假如后续还有其它支付方式的增加是不是每次都要增加一个if语句呢?这么做有什么弊端呢?
上述修改方式对现有的代码进行了修改,有潜在的危险,因为我们的pay方法在新增支付方式之前已经测试过并上线,你新增了一个支付方式对其进行了修改,是不是还要重新测试一遍呢?
另外这种写法其实违背了开闭原则即“对扩展开放,对修改关闭”,哪里违背了呢?其实就是对扩展支持的不好,新增一种支付方式需要修改核心代码逻辑风险很大。那么我们应该怎么去设计它呢?案例一中可以了解到什么是面向抽象而非具体编程,同样Shape接口一样是不是可以对支付方式进行抽象呢?当然,可以定义一个接口Payment在其中抽象一个pay方法用来完成支付功能,它的代码如下
public interface Payment {public void pay();
}
所有的支付方式都需要实现此接口,当前只支持AliPay和WechatPay这两种支付方式,它们的代码如下
public class AliPay implements Payment {@Overridepublic void pay() {System.out.println("调用 alipay 接口");}
}public class WechatPay implements Payment {@Overridepublic void pay() {System.out.println("调用 WechatPay 接口");}
}
PayManager的代码如下
public class PayManager {// 依赖注入public void pay(Payment payment) {payment.pay();}
}
然后就是如何去调用
public class TestPay {public static void main(String[] args) {Payment wechatPay = new WechatPay();PayManager.pay(wechatPay);}
}
可以看到第三行需要哪种支付方式直接创建其对象并将其传递个PayManager的pay方法即可。此时需要增加一个银行卡支付功能应该如何处理呢?只需要增加一个BankCardPay类并实现Payment接口即可,代码如下
public class BankCardPay implements Payment {@Overridepublic void pay() {System.out.println("调用 BankCardPay 接口");}
}
这样就已经修改完成,调用方法也跟上面一样,代码如下
public class TestPay {public static void main(String[] args) {Payment wechatPay = new BankCardPay();PayManager.pay(wechatPay);}
}
可以看到增加一种BankCardPay支付方式并未对PayManager类做修改,因此对之前已经上线的功能没有影响。后续再增加其它支付方式比如京东支付、抖音支付等等都很容易扩展。
可能有些同学会说,这样修改增加很多个类可读性还没有之前好,确实如此,有些情况下代码的扩展性会跟可读性相冲突,比如上面我们重构代码之后,可读性没有之前的if分支好,所以很多时候我们要在扩展性和可读性之间做权衡,在某些场景下代码的可读性很重要,我们就牺牲一些扩展性,在某些场景下代码的扩展性很重要,我们就牺牲一些可读性。
比如上述的例子,如果项目一开始就说我们的支付方式只支持Alipay和WechatPay这两种支付方式,那刚开始的写法思路简单易读,它就是合理的。相反如果后续要增加很多种支付方式,那么我们重构之后的写法就是比较合理的,因此是否合理没有一个统一的标准,一定要结合业务需求、场景等来进行重构。
三、总结
1.定义
软件实体如类、模块和函数应该对扩展开放,对修改关闭。
开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代码来完成新的功能开发,低层模块的变更,必然要有高层模块进行耦合,否则就是一个独立无意义的代码片段。
2.为什么要使用开闭原则
(1)对于测试来讲,新增的方法不会对已有的方法造成影响,只需要保证新增的类、模块和函数是正确的就可以了
(2)提高可复用性,在面向对象的设计中,所有的逻辑都是从原子逻辑组合而来的,而不是在一个类中独立实现一个业务逻辑。只有这样代码才可以复用,粒度越小,被复用的可能性就越大。
(3)提高可维护性,从上述例子中可以看出来当对已有功能做修改时增加一个类即可,这是不是就是维护人员很乐意干的事,即增加一个类,而不是修改一个类。
3.如何做到"对扩展开放、对修改关闭"
添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。我们要时刻具备扩展意识、抽象意识、封装意识,在写代码的时候要多花点时间思考一下,未来可能的变更,以便在未来需求变更的时候,在不改变代码整体结构的情况下,将新的代码灵活的插入到项目中。
很多设计原则、设计思想、设计模式都是以提到代码的扩展性为最终目的。特别是23中经典设计模式,大部分都是为了解决代码的扩展性而总结出来的,都是以开闭原则为指导原则。最常用提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(装饰、策略、模版、责任链、状态)
好了本篇博客就到这里了,后续还会继续更新关于设计原则和设计模式相关的文章,如果觉得对你有用,帮忙点赞回复666
参考书籍:
《设计模式之禅》
《架构整洁之道》
相关文章:
SOLID-开闭原则
单一职责原则:https://blog.csdn.net/dmk877/article/details/143447010 在前面我们学习了单一职责原则,今天来一起学习一下SOLID原则中的开闭原则(Open-Closed Principle, OCP) 通过本篇博客你将学到到以下内容 ①什么是开闭原则 ②如何实现开闭原则 ③…...
前端经典面试合集(二)——Vue/React/Node/工程化工具/计算机网络
1. 说说 Vue 中的 Diff 算法 Vue 的 Diff 算法 主要用于优化虚拟 DOM 和实际 DOM 之间的比较过程。它通过以下几种策略来提高性能: 最小化对 DOM 的操作:Vue 通过在内存中构建一个虚拟 DOM 树,在虚拟 DOM 树与真实 DOM 树之间进行比较和更新…...
PH47代码框架 24241231 重要更新
仪式感一下:2024年最后一天,发布 PH47 代码框架的一次重要更新。当然这并不是有意的,而是直到现在才把更新的所有工作全部做完(希望确实如此)。 本次更新要点: 1、加入多IMU支持。本次更新正式加入对 MPU65…...
Qt6之QML——作用域
作用域定义了表达式可以访问哪些变量、属性或对象,并决定了在变量重名时的优先级如何处理。以下将从作用、绑定、组件作用域和使用技巧四个方面详细解析 QML 中的作用域。 1. 作用:表达式的变量访问与优先级 在 QML 中,表达式能够访问的变量…...
119.【C语言】数据结构之快速排序(调用库函数)
目录 1.C语言快速排序的库函数 1.使用qsort函数前先包含头文件 2.qsort的四个参数 3.qsort函数使用 对int类型的数据排序 运行结果 对char类型的数据排序 运行结果 对浮点型数据排序 运行结果 2.题外话:函数名的本质 1.C语言快速排序的库函数 cplusplus网的介绍 ht…...
C#封送类
封送类(Marshaling classes)在.NET框架中扮演着至关重要的角色,尤其是在托管代码与非托管代码之间进行数据交换时。封送过程涉及到将托管环境中的对象转换为非托管环境中可以理解的形式,并且反之亦然。这一过程确保了两种不同类型…...
2024年度学习总结
2024年是我学业生涯的结束,是我职业生涯的开始。2024年6月19日我顺利研究生毕业,进入体制内,陆止于此,海始于斯,知识和文化最大的魅力,大概就是教会人谦卑和敬畏。读研的目的不是为了单纯拿到哪个证书&…...
我的博客年度之旅:感恩、成长与展望
目录 感恩有你 技能满点 新年新征程 嘿,各位技术大佬、数码潮咖还有屏幕前超爱学习的小伙伴们!当新年的钟声即将敲响,我们站在时光的交汇点上,回首过往,满心感慨;展望未来,豪情满怀。过去的这…...
undefined symbol: __nvJitLinkComplete_12_4, version libnvJitLink.so.12
目录 我的解决方法: 测试: 报错: undefined symbol: __nvJitLinkComplete_12_4, version libnvJitLink.so.12 from torch._C import * # noqa: F403 ImportError: /mnt/pfs/users/lbg/envs/mmpano/lib/python3.9/site-packages/torch/lib…...
【OTA】论文笔记--《智能网联汽车整车OTA功能设计研究》智能网联汽车OTA系统设计分析报告
智能网联汽车OTA系统设计分析报告 引言 随着汽车智能化、网联化水平不断提升,现代汽车中电子控制单元(ECU)的数量和复杂度持续增加。据统计,高级轿车上电子电气元件的成本已占整车开发成本的60%~70%。为了实现对这些电控单元的软件开发调试、数据标定、文件更新和故障修复,…...
c#String和StringBuilder
目录 一,String 1,string的特点: 2,string常用方法 (1)Length (2)Substring() (3)ToUpper() (4)ToLower() (5&…...
【Linux】HTTP协议
之前,我们已经做过了自定义协议,事实上,已经有很多现成已经做好又非常好用的协议,它们都是相同的,比如HTTP协议。所谓HTTP协议,就是超文本传输协议,定义了客户端和服务器之间是如何通信的&#…...
计算机网络 (14)数字传输系统
一、定义与原理 数字传输系统,顾名思义,是一种将连续变化的模拟信号转换为离散的数字信号,并通过适当的传输媒介进行传递的系统。在数字传输系统中,信息被编码成一系列的二进制数字,即0和1,这些数字序列能够…...
《向量数据库指南》——Milvus Cloud 2.5:Sparse-BM25引领全文检索新时代
Milvus Cloud BM25:重塑全文检索的未来 在最新的Milvus Cloud 2.5版本中,我们自豪地引入了“全新”的全文检索能力,这一创新不仅巩固了Milvus Cloud在向量数据库领域的领先地位,更为用户提供了前所未有的灵活性和效率。作为大禹智库的向量数据库高级研究员,以及《向量数据…...
Unity3D 网络框架设计详解
前言 Unity3D是一款强大的跨平台游戏开发引擎,网络框架的设计对于实现客户端与服务器之间的稳定通信至关重要。本文将详细介绍Unity3D网络框架的设计原理、技术要点以及代码实现。 对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一…...
网络渗透测试实验四:CTF实践
1.实验目的和要求 实验目的:通过对目标靶机的渗透过程,了解CTF竞赛模式,理解CTF涵盖的知识范围,如MISC、PPC、WEB等,通过实践,加强团队协作能力,掌握初步CTF实战能力及信息收集能力。熟悉网络扫描、探测HTTP web服务、目录枚举、提权、图像信息提取、密码破解等相关工具…...
Wend看源码-Java-Collections 工具集学习
摘要 java.util.Collections它提供了一系列静态方法,用于对集合(如List、Set、Map等)进行操作。这些操作包括排序、查找、替换、同步等多种功能,帮助开发者更方便地处理集合数据。以下是Collections 提供的一些主要方法的总结。…...
[JAVA]MyLogger
import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.*;/*** 可以自已定义日志打印格式,这样看起来比较方便些**/ class MyFormatter extends Formatter {Overridepublic String format(LogRecord ar…...
玩转OCR | 腾讯云智能结构化OCR初次体验
目录 一、什么是OCR(需要了解) 二、产品概述与核心优势 产品概述 智能结构化能做什么 举例说明(选看) 1、物流单据识别 2、常见证件识别 3、票据单据识别 4、行业材料识别 三、产品特性 高精度 泛化性 易用性 四、…...
记一次 dockerfile 的循环依赖错误
文章目录 1. 写在最前面1.1 具体循环依赖的例子 2. 报错的位置2.1 代码快速分析2.2 代码总结2.3 关于 parser 的记录 3. 碎碎念 1. 写在最前面 笔者在使用 dockerfile 多阶段构建的功能时,写出了一个「circular dependency detected on stage: xx」的错误。 解决方…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...
多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
