03.开闭原则详细介绍

news/2025/2/13 9:45:40/文章来源:https://www.cnblogs.com/yc211/p/18712504

03.开闭原则详细介绍

目录介绍

  • 01.问题思考的分析
  • 02.如何理解开闭原则
  • 03.开闭原则的背景
  • 04.开闭原则比较难学
  • 05.实现开闭原则方式
  • 06.画图形案例分析
  • 07.银行业务案例分析
  • 08.开闭原则优缺点
  • 09.开闭原则的总结

推荐一个好玩网站

一个最纯粹的技术分享网站,打造精品技术编程专栏!编程进阶网

https://yccoding.com/

设计模式Git项目地址:https://github.com/yangchong211/YCDesignBlog

本文摘要介绍

本文详细介绍了面向对象设计中的开闭原则(OCP),即软件实体应对扩展开放、对修改关闭。文章通过问题思考、背景分析、实现方式及案例教学,深入探讨了如何在实际开发中应用这一原则,以提高代码的可维护性和扩展性。

文中还结合图形绘制和银行业务两个具体案例,展示了如何通过抽象类、接口、策略模式等技术手段实现开闭原则。最后总结了开闭原则的优缺点,并强调了其在设计模式中的重要地位。

01.问题思考的分析

  1. 什么叫作开闭原则,他的主要用途是什么?
  2. 如何做到拓展开放,修改封闭这一准则,结合案例说一下如何实现?
  3. 你平常是如何理解开闭原则的,判断的标准是什么?

02.如何理解开闭原则

开闭原则的英文全称是 Open Closed Principle,简写为 OCP。它的英文描述是:software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。

我们把它翻译成中文就是:软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。

这个描述比较简略,如果我们详细表述一下,那就是,添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)

03.开闭原则的背景

在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会将错误引入原本已经经过测试的旧代码中,破坏原有系统。

因此,当软件需要变化时,我们应该尽量通过扩展的方式来实现变化,而不是通过修改已有的代码来实现。

当然,在现实开发中,只通过继承的方式来升级、维护原有系统只是一个理想化的愿景,因此,在实际的开发过程中,修改原有代码、扩展代码往往是同时存在的。

软件开发过程中,最不会变化的就是变化本身。产品需要不断地升级、维护,没有一个产品从第一版本开发完就再没有变化了,除非在下个版本诞生之前它已经被终止。

产品需要升级,修改原来的代码就可能会引发其他的问题。那么如何确保原有软件模块的正确性,以及尽量少地影响原有模块,答案就是尽量遵守本章要讲述的开闭原则

04.开闭原则比较难学

个人觉得,开闭原则是 SOLID 中最难理解、最难掌握,同时也是最有用的一条原则。

之所以说这条原则难理解,那是因为,“怎样的代码改动才被定义为‘扩展’?怎样的代码改动才被定义为‘修改’?怎么才算满足或违反‘开闭原则’?修改代码就一定意味着违反‘开闭原则’吗?”等等这些问题,都比较难理解。

之所以说这条原则难掌握,那是因为,“如何做到‘对扩展开放、修改关闭’?如何在项目中灵活地应用‘开闭原则’,以避免在追求扩展性的同时影响到代码的可读性?”等等这些问题,都比较难掌握。

之所以说这条原则最有用,那是因为,扩展性是代码质量最重要的衡量标准之一。在 23 种经典设计模式中,大部分设计模式都是为了解决代码的扩展性问题而存在的,主要遵从的设计原则就是开闭原则。

05.实现开闭原则方式

为了实现开闭原则,常用的设计技术有以下几种:

  1. 抽象类和接口:通过定义抽象类和接口来约定行为,然后通过继承和实现这些抽象类和接口来扩展功能。
  2. 策略模式:将算法的实现分离到不同的类中,通过组合方式来实现不同的行为。
  3. 装饰器模式:通过对对象进行包装,动态地添加新的行为或功能。

06.画图形案例分析

6.1 普通案例实现

假设有一个图形绘制程序,程序需要能够绘制不同形状的图形,比如矩形、圆形和三角形。最初的设计可能会像这样:

public class GraphicEditor {public void draw(Shape shape) {if (shape.m_type == 1) {drawRectangle();} else if(shape.m_type == 2) {drawCircle();}}public void drawRectangle() {System.out.println("画长方形");}public void drawCircle() {System.out.println("画圆形");}class Shape {int m_type;}class Rectangle extends Shape {Rectangle() {super.m_type=1;}}class Circle extends Shape {Circle() {super.m_type=2;}}
}

我们来看看,这个代码,初看是符合要求了,再想想,要是我增加一种形状呢? 比如增加三角形.

  1. 首先,要增加一个三角形的类, 继承自Shape
  2. 第二,要增加一个画三角形的方法drawTriage()
  3. 第三,在draw方法中增加一种类型type=3的处理方案

在这个设计中,每当我们需要添加新的图形类型,就需要修改 GraphicEditor 类,添加新的 if 条件。我们需要修改已有的代码来实现新功能。

这就违背了开闭原则-对扩展开发,对修改关闭。增加一个类型,修改了三处代码。

6.2 开闭原则演变

为了符合开闭原则,我们可以进行重构。首先,我们定义一个抽象类Shape

public class GraphicEditor {public void draw(Shape shape) {shape.draw();}interface Shape {void draw();}class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("画矩形");}}class Circle implements Shape {@Overridepublic void draw() {System.out.println("画圆形");}}}

各种类型的形状自己规范自己的行为, 而GraphicEditor.draw()只负责画出来. 当增加一种类型三角形. 只需要

  1. 第一,增加一个三角形的类,实现Shape接口
  2. 第二,调用draw方法,划出来就可以了

整个过程都是在扩展,而没有修改原来的类。这个设计是符合开闭原则的。

6.3 使用例子分析

让我们来看一个具体的使用例子,展示如何遵循开闭原则来进行扩展。

public class Main {public static void main(String[] args) {Shape circle = new Circle();Shape rectangle = new Rectangle();GraphicEditor editor = new GraphicEditor();editor.drawShape(circle);editor.drawShape(rectangle);}
}

在这个例子中,我们创建了一个圆形对象和一个矩形对象,并通过 GraphicEditor 类来绘制它们。当我们需要添加新的图形类型(例如三角形)时,只需创建一个新的类实现 Shape 接口,而不需要修改现有的代码:

// 三角形类
public class Triangle implements Shape {@Overridepublic void draw() {// 绘制三角形的代码}
}// 使用新的三角形类
public class Main {public static void main(String[] args) {Shape circle = new Circle();Shape rectangle = new Rectangle();Shape triangle = new Triangle();GraphicEditor editor = new GraphicEditor();editor.drawShape(circle);editor.drawShape(rectangle);editor.drawShape(triangle);}
}

通过这种方式,我们可以在不修改 GraphicEditor 类的情况下,轻松地扩展新的图形类型,真正实现了对扩展开放,对修改关闭的设计原则。

07.银行业务案例分析

7.1 业务基础实现

比如现在有一个银行业务,存钱,取钱和转账。最初我们会怎么思考呢?

  1. 首先有一个银行业务类, 用来处理银行的业务
  2. 银行有哪些业务呢? 存钱,取钱,转账, 这都是银行要执行的操作
  3. 那外部说我要存钱, 我要取钱,我要转账, 通过一个类型来告诉我们
public class BankBusiness {public void operate(int type) {if (type == 1) {save();} else if(type == 2) {take();} else if(type == 3) {transfer();}}public void save(){System.out.println("存钱");}public void take(){System.out.println("取钱");}public void transfer() {System.out.println("转账");}
}

咋一看已经实现了需求. 但是现在有新的需求来了, 银行要增加功能---理财. 理财是银行业务的一种, 自然是新增一个方法.

然后在operate()方法里增加一种类型. 这就是一个糟糕的设计, 增加新功能, 但是却修改了原来的代码

7.2 开闭原则演变

设计成接口抽象的形式,利用开关原则,可以尝试改造为下面的代码。将不同对象分类的服务方法进行抽象,把业务逻辑的紧耦合关系拆开,实现代码的隔离保证了方便的扩展。

public interface Business {public void operate();
}public class Save implements Business {@Overridepublic void operate() {System.out.println("存钱业务");}
}public class Take implements Business {@Overridepublic void operate() {System.out.println("取钱业务");}
}public class Transfer implements Business {@Overridepublic void operate() {System.out.println("转账业务");}
}public class BankBusinesses {/*** 处理银行业务* @param business*/public void operate(Business business) {System.out.println("处理银行业务");business.operate();}
}

通过接口抽象的形式方便扩展, 加入要新增理财功能. 只需新增一个理财类, 其他业务代码都不需要修改.

其实, 在日常工作中, 经常会遇到这种情况. 因为我们平时写业务逻辑会更多一些, 而业务就像流水账, 今天一个明天一个一点一点的增加. 所以,当业务增加到3个的时候, 我们就要思考, 如何写能够方便扩展

08.开闭原则优缺点

8.1 开闭原则的优点

  1. 提高了代码的可维护性与复用性:遵循开闭原则可以让代码更加稳定和可维护,同时也使得代码更容易被复用。如果我们需要修改某个模块的行为,只需要扩展该模块而不需要直接修改源代码,这样就不会影响到其他的模块。
  2. 提高了代码的可扩展性:开闭原则还可以提高代码的可扩展性。通过扩展已有的代码,我们可以很容易地添加新的功能或改进现有功能,从而适应业务需求的更改。
  3. 提高了代码的可测试性:遵循开闭原则可以降低代码的耦合度,使得测试更加容易。因为我们只需要测试新增的代码,而不必验证已有代码的正确性。

8.2 开闭原则的缺点

  1. 对代码的设计要求高:遵循开闭原则需要对代码进行良好的抽象和封装,这对程序员的设计能力和经验要求较高。如果设计不好,可能会导致代码过于复杂难以维护。
  2. 可能会增加代码量:通过扩展已有的代码来实现新功能,可能会增加代码量,使得系统变得更加复杂。这需要我们在平衡可维护性和代码量之间做出权衡。
  3. 可能会带来设计上的限制:在某些情况下,为了遵循开闭原则,我们可能需要引入更多的抽象层或接口。这可能会带来一定的设计上的限制,限制了代码的表达能力和灵活性。

09.开闭原则的总结

9.1 理解开闭原则

如何理解“对扩展开放、对修改关闭”?

添加一个新的功能,应该是通过在已有代码基础上扩展代码(新增模块、类、方法、属性等),而非修改已有代码(修改模块、类、方法、属性等)的方式来完成。

关于定义,我们有两点要注意。

  1. 第一点是,开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。
  2. 第二点是,同样的代码改动,在粗代码粒度下,可能被认定为“修改”;在细代码粒度下,可能又被认定为“扩展”。

9.2 如何做到开闭原则

如何做到“对扩展开放、修改关闭”?

我们要时刻具备扩展意识、抽象意识、封装意识。在写代码的时候,我们要多花点时间思考一下,这段代码未来可能有哪些需求变更,如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,在不改动代码整体结构、做到最小代码改动的情况下,将新的代码灵活地插入到扩展点上。

学习设计原则,要多问个为什么。

不能把设计原则当真理,而是要理解设计原则背后的思想。搞清楚这个,比单纯理解原则讲的是啥,更能让你灵活应用原则。

9.3 开闭原则的总结

  1. 问题思考:开闭原则的主要用途是什么?如何才能做到对外拓展开放,对内修改关闭?你平常是如何理解开闭原则的,判断的标准是什么?
  2. 如何理解开闭原则:软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。
  3. 为何开闭原则比较难学:怎样的代码改动才被定义为‘修改’?怎么才算满足或违反‘开闭原则’?如何理解大部分设计模式都是遵循开闭原则!
  4. 开笔原则的背景:软件/业务迭代升级中,面对代码变化,修改原来代码可能引入原有模块bug风险,因此尽量通过对外拓展来实现功能迭代。
  5. 实现开闭原则的方式:常用的设计技术有以下几种,1.抽象类和接口;2.策略模式;3.装饰器模式等。
  6. 开闭原则的案例教学:绘制圆形/矩形,通过if-else来执行不同case逻辑,新增一种类型则需要修改原逻辑。因此考虑通过接口+抽象类方式实现友好拓展。
  7. 开闭原则的优点:1.提高代码拓展性和可维护性;2.提高代码可测试的粒度;3.降低的代码耦合度。
  8. 开闭原则的缺点:1.对代码设计要求高;2.可能会导致代码量增大和变得复杂;3.可能会带来设计上的限制。
  9. 总结如何理解开闭原则:当需求发生变化时,我们应该通过添加新的代码来扩展功能,而不是修改已有的代码。
  10. 总结如何运用开闭原则:通过封装变化、使用抽象化、利用扩展点和遵循依赖倒置原则来实现。

10.更多内容推荐

模块 描述 备注
GitHub 多个YC系列开源项目,包含Android组件库,以及多个案例 GitHub
博客汇总 汇聚Java,Android,C/C++,网络协议,算法,编程总结等 YCBlogs
设计模式 六大设计原则,23种设计模式,设计模式案例,面向对象思想 设计模式
Java进阶 数据设计和原理,面向对象核心思想,IO,异常,线程和并发,JVM Java高级
网络协议 网络实际案例,网络原理和分层,Https,网络请求,故障排查 网络协议
计算机原理 计算机组成结构,框架,存储器,CPU设计,内存设计,指令编程原理,异常处理机制,IO操作和原理 计算机基础
学习C编程 C语言入门级别系统全面的学习教程,学习三到四个综合案例 C编程
C++编程 C++语言入门级别系统全面的教学教程,并发编程,核心原理 C++编程
算法实践 专栏,数组,链表,栈,队列,树,哈希,递归,查找,排序等 Leetcode
Android 基础入门,开源库解读,性能优化,Framework,方案设计 Android

23种设计模式

23种设计模式 & 描述 & 核心作用 包括
创建型模式
提供创建对象用例。能够将软件模块中对象的创建和对象的使用分离
工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
结构型模式
关注类和对象的组合。描述如何将类或者对象结合在一起形成更大的结构
适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
过滤器模式(Filter、Criteria Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)
行为型模式
特别关注对象之间的通信。主要解决的就是“类或对象之间的交互”问题
责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
空对象模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
访问者模式(Visitor Pattern)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/883089.html

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

相关文章

打靶记录27——Tre

靶机: https://www.vulnhub.com/entry/tre-1,483/ 下载(镜像):https://download.vulnhub.com/tre/Tre.zip 难度:中目标:获得 Root 权限 + Flag攻击方法:主机发现 端口扫描 信息收集 进阶路径枚举 EXP 代码改造 突破边界方法1 突破边界方法2 突破边界方法3 权限提升主机发…

7. Docker 容器数据卷的使用(超详细的讲解说明)

7. Docker 容器数据卷的使用(超详细的讲解说明) @目录7. Docker 容器数据卷的使用(超详细的讲解说明)1. Docker容器数据卷概述2. Docker 容器数据卷的使用演示:2.1 宿主 和 容器之间映射添加容器卷2.2 容器数据卷 读写规则映射添加说明2.3 容器数据卷的继承和共享3. 最后:坑:…

到底值不值得本地部署残血版DeepSeek?一文说清!教你如何白嫖满血版DeepSeek

一、介绍最近一段时间,DeepSeek 备受关注,夏天也向身边朋友推荐。但它常无法使用,原因是受到大规模恶意攻击,且 IP 地址在美国。 通过以下网站可以查看DeepSeek网站状态: status.deepseek.com/ 可以发现,最近标红的就是故障中​若遇服务器繁忙提示,大概率是被攻击了,并…

干货:DeepSeek+SpringAI实现流式对话!

前面一篇文章我们实现了《炸裂:SpringAI内置DeepSeek啦!》,但是大模型的响应速度通常是很慢的,为了避免用户用户能够耐心等待输出的结果,我们通常会使用流式输出一点点将结果输出给用户。 那么问题来了,想要实现流式结果输出,后端和前端要如何配合?后端要使用什么技术实…

Ftrans文件安全外发系统,为企业数据保驾护航!

随着企业的不断发展,集团分公司及各部门需向外部客户、合作伙伴及海外同事外发文件。过去,主要通过邮件、FTP方式将数据进行外发,主要存在以下问题和挑战: 1.进行文件外发时需通过OA的审批,由于OA审批与FTP传输两个环节割裂,公司无法有效限制数据外发范围和管控数据外发安…

本地部署 DeepSeek:小白也能轻松搞定!

大家好,我是晓凡。 写在前面 最近DeepSeek太火了,以至于每个小伙伴都想试试。DeepSeek 的到来可谓是开启了全民AI热潮。 本以为DeepSeek本地化部署有多难,实际上验证后很简单,操作起来就像给电脑装个新软件那么简单,大约十多分钟可完成本地部署。 今天咱们来聊聊如何在自己…

Git指南-从入门到精通

代码提交和同步命令 流程图如下:第零步: 工作区与仓库保持一致 第一步: 文件增删改,变为已修改状态 第二步: git add ,变为已暂存状态$ git status $ git add --all # 当前项目下的所有更改 $ git add . # 当前目录下的所有更改 $ git add xx/xx.py xx/xx2.py # 添加某几个…

一分钟搞定!CentOS 7.9上用Ansible自动化部署SQL Server 2019

一分钟搞定!CentOS 7.9上用Ansible自动化部署SQL Server 2019不熟悉整个流程的朋友可以先看之前的部署文章,手动部署一遍 一步步教你在CentOS 7.9上安装SQL Server 2019前言 这套Ansible脚本属于红帽官方出品,是一套mssql的自动化运维脚本,能够实现mssql的单实例部署和Alwa…

【Linux】Linux如何查看JDK的安装路径

如何在一台Linux服务器上查找JDK的安装路径呢?有那些方法可以查找定位JDK的安装路径?是否有一些局限性呢?下面总结了一下如何查找JDK安装路径的方法. 1、echo $JAVA_HOME 使用$JAVA_HOME的话能定位JDK的安装路径的前提是配置了环境变量$JAVA_HOME,否则如下所示,根本定位不…

[虚拟化/Docker] Docker Desktop 安装与使用

0 序:DeepSeek 等AI大模型在Windows的私有化部署DeepSeek 等AI大模型在Windows的私有化部署,最流行的开源AI终端应用————Dify,依赖于 Docker 环境。由此,必然离不开:Docker Desktop1 概述:Docker Desktopdocker desktop 是一款Docker容器运行管理工具,用于在本地机器…

【VUE框架】渗透测试的一些技巧(实现自动化测试)

以下文章来源于渗透测试之道 ,作者kk1230 一、什么是VUEVue.js 是一个渐进式 JavaScript 框架,用于构建用户界面。而 Webpack 是一个模块打包工具,用于将项目中的各种资源(如 JavaScript 模块、CSS 样式文件、图片等)打包成浏览器可以识别的文件。 Webpack概念机制Webpack…

案例 百万数据批量插入测试

1、jpa、mybatis-plus、jdbc、load data infile测试比较2、load data infile语法测试3、相关代码 测试package com.xm;import com.xm.entity.UserP; import com.xm.task.JdbcInsert2Task; import com.xm.task.JdbcInsertTask; import com.xm.task.MybatisPlusInsert2Task; impo…