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」的错误。 解决方…...

Trimble天宝X9三维扫描仪为建筑外墙检测提供了全新的解决方案【沪敖3D】
随着城市化进程的快速推进,城市高层建筑不断增多,对建筑质量的要求也在不断提高。建筑外墙检测,如平整度和垂直度检测,是衡量建筑质量的重要指标之一。传统人工检测方法不仅操作繁琐、效率低下,还难以全面反映墙体的真…...

【MySQL】深度学习数据库开发技术:使用CC++语言访问数据库
**前言:**本节内容介绍使用C/C访问数据库, 包括对数据库的增删查改操作。 主要是学习一些接口的调用, 废话不多说, 开始我们的学习吧! ps:本节内容比较容易, 友友们放心观看哦! 目录 准备mysql…...

LabVIEW化工实验室设备故障实时监测
化工实验室中,各类设备的运行状态直接影响实验的精度与安全性。特别是重要分析仪器的突发故障,可能导致实验中断或数据失效。为了实现设备运行状态的实时监控与故障快速响应,本文提出了一套基于LabVIEW的解决方案,通过多参数采集、…...

单例模式懒汉式、饿汉式(线程安全)
饿汉式单线程安全吗 饿汉式单例(Eager Singleton)是线程安全的。这种实现方式在类加载时就创建了单例实例,因此在多线程环境中,不存在多个线程同时创建实例的问题。 饿汉式单例的实现 以下是一个饿汉式单例的示例: …...

Cursor登录按钮点击没反应
问题 系统:Windows11 Cursor:Cursor 0.44.9 当安装Cursor打开进行登录时,点击Sign in没反应 解决方案 1.打开window11的设置 2.点击应用中的默认应用 3.在设置应用程序的默认值中搜索Google(没有Google浏览器的尝试下载一个&a…...

论文实现:Reactive Nonholonomic Trajectory Generation via Parametric Optimal Control
1. 多项式螺旋 曲率: κ ( s ) a 0 a 1 s a 2 s 2 a 3 s 3 a 4 s 4 a 5 s 5 \begin{align} \kappa(s) a_0 a_1s a_2s^2 a_3s^3 a_4s^4 a_5s^5 \end{align} κ(s)a0a1sa2s2a3s3a4s4a5s5 机器人朝向: θ ( s ) a 0 s a 1 …...

基于单片机中药存放环境监测系统的实现
基于单片机中药存放环境监测系统的实现 项目开发背景 随着现代中药的广泛应用,中药材的存储环境对其质量有着至关重要的影响。温湿度、烟雾、火灾等环境因素,若不加以控制,将会导致中药材失效或变质。因此,设计一个基于单片机的…...

九垠赢+商业管理系统 Common.ashx 文件上传致RCE漏洞复现
0x01 产品简介 九垠赢+商业管理系统是基于互联网技术的进销存管理软件,适用于新零售背景下各种业态的线上线下一体化的商超经营管理。赢+ERP以商业管理系统为底座,融合了多种软、硬件解决方案,实现了从企业、供应商、三方平台到顾客等日常管理的全线数字化、智能化和移动化…...

速盾:服务器CDN加速解析的好处有哪些呢?
随着互联网应用的普及,越来越多的企业开始关注如何提升网站的访问速度和用户体验。为了实现这一目标,许多企业选择使用CDN(内容分发网络)来加速网站的内容分发。CDN通过在全球范围内分布多个节点,将内容缓存到离用户最…...

C++ 设计模式:备忘录模式(Memento Pattern)
链接:C 设计模式 链接:C 设计模式 - 状态模式 备忘录模式(Memento Pattern)是一种行为设计模式,它允许在不破坏封装性的前提下捕获和恢复对象的内部状态。这个模式在需要保存和恢复对象状态的场景中非常有用ÿ…...