05.开闭原则(Open Closed Principle)

2023-12-13 08:45

本文主要是介绍05.开闭原则(Open Closed Principle),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

“你这个人怎么这么轴?让你改改以前的代码怎么和要了你命似的?难道你的能力仅限于此吗?”
“你懂什么?我有我的原则!我有我的信仰!”

一言

开闭原则即:对扩展开放,对修改关闭,它是所有设计的核心。


概述

大家潜意识里普遍遵循的原则,往往是最关键、最重要的原则。法律或许偶有狂徒触碰,但雷池从不放走一个活人。

科幻小说《三体》中有这样一个桥段,面壁者比尔·希恩斯为了“坚定”地球人抵抗三体人侵略的决心,发明了可以改变人类潜意识的机器,并取名思想钢印。甚至当在机器中输入“水是有毒的”这样的理论也可以灌注到人类的基本意志中,比尔希尔斯本人也险些因此拒绝喝水,脱水而亡。

小说的描述颇具浪漫主义气质,事实上,开闭原则就是软件设计中难以推翻的“思想钢印”。
如果单说开闭原则,或许有些同学会一时发懵不知道是在说什么。但如果换种通俗的说法:之前的需求要你实现了30个工具方法,随着项目的丰满,这30个工具方法在各个实现中调用了差不多100次。现在有个狼人过来悄咪咪告诉你,“兄弟,这30个工具方法你把实现细节全改一改,我有大用!” 你会有什么反应?
相信大多数同学都能心领神会的口径一致:“GUN!
“呀?还会说英文呢?”
“拼音或者英文都可以表达我的态度,具体看你理解!”
狼人不理解了,为什么这么大反应?其实广大程序员这种刻在骨子里的反应就是开闭原则的思想钢印,大多数人潜意识里都在恪守。


何为开闭原则

一个软件实体,模块和函数应该对扩展开放,对修改关闭。用抽象构建框架,用实现扩展细节。当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现优化。

  • 对扩展开放: 对提供功能的一方扩展开放,可以添加功能或类;
  • 对修改关闭: 对使用方而言,修改是关闭的(比如新增了一个类,不会修改之前使用方的代码/功能);

三寸反骨

碰了璧的狼人黑夜化身反骨仔,挑灯加班决定突破开闭原则的思想钢印,在不遵守开闭原则的前提下,写了一段代码。

需求很简单,实现一个画图形的工具类。

反例代码

public class Graph {class Editor{public void draw(Shape shape){if (shape.type==1)drawCircle(shape);else if (shape.type==2)drawBlock(shape);}public void drawCircle(Shape shape){System.out.println("绘制圆形具体实现");}public void drawBlock(Shape shape){System.out.println("绘制方形具体实现");}}class Shape{int type;}class Circle extends Shape{Circle(){super.type =1;}}class Block extends Shape{Block(){super.type = 2;}}
}

反骨仔开始志得意满,这也太简单了,丝毫没有压力啊。于是此方法投入使用了半年,借助这个工具类,同事们搭建了一个又一个“完美且稳定”的系统,牵扯调用三百多次。
然后,好消息来了,绘图工具需求增加,要求在原有基础上支持对三角形、平行四边形、梯形、五角星、六芒星、小波浪、大波浪等等一百二十多个图形的绘制。
反骨仔凌乱了,因为他的绘制方法实现要做大量修改姑且不论,同事们调用这个方法高达三百余处,现在底层方法修改了,上下文逻辑是不是都需要再推敲,回归测试的工作量有多少。这一些列操作下来的成本又由谁来承担?
所以说,雷池就是雷池,轻易不要头铁,开闭原则能被广大同学在潜意识中遵守一定有他的道理。在软件开发的过程中,也许我不比所有人聪明,但是我一定要是最稳的那个。


优化设计

打开时光机,反骨仔脱胎换骨回到了半年前那个倔强的夜晚,默默捧起了《程序员的自我修养》,开始遵循OCP原则,对那个“简单的需求”进行设计。
优化代码

public class Ocp {public static void main(String[] args) {//使用,看看存在的问题GraphEditor graphEditor = new GraphEditor();graphEditor.drawShape(new Block());graphEditor.drawShape(new Circle());graphEditor.drawShape(new Star());}
}//绘图类
class GraphEditor{//接收Shape对象,根据type绘制不同图形public void drawShape(Shape s){s.draw();}
}//图形基类
abstract class Shape{public abstract void draw();//抽象方法
}//矩形类
class Block extends  Shape{@Overridepublic void draw() {System.out.println("绘制方块");}
}//圆形类
class Circle extends  Shape{@Overridepublic void draw() {System.out.println("绘制圆形");}
}//圆形类
class Star extends  Shape{@Overridepublic void draw() {System.out.println("绘制五角星");}
}

把创建Shape类做成抽象类,并提供一个抽象的draw方法,让子类实现即可,这样我们有新的图形种类时,只需要新的图形类继承Shape,并实现draw方法即可,使用的代码就不需要修改,这就满足了OCP原则。
果然,命运的齿轮在半年后再一次转到了曾经的至暗时刻,海量的图形扩展需求蜂拥而至,而此时的那个少年成竹在胸,微微一笑,说到:
“大家放心,已经在用的方法不会有任何变更。扩展的问题交给我,相关模块的回归测试理论上可以不用做,当然,具体的施行还是看各部门的落地要求。”


开闭原则是所有设计的核心,更是所有优雅扩展的集中表现形式。从广义上讲,它其实是在寻求一种变中求不变的平衡。开闭原则并不是拒绝需求的扩张,相反,它恰恰是为了以最小代价满足需求扩张而形成的捷径。从另一个角度看,它恰恰印证了软件开发生命周期中,设计阶段的重要性。
在上文的论述中,为了更好的表述OCP原则,我们一直尝试着从正向解决问题。其实我们忽略了一个角度,“反骨仔”遇到的难题一方面是由于他没有遵循OCP原则,那么其他同事有没有问题呢?一个工具类牵扯到几百个模块的调用是否真的合理呢?
这里其实就又牵出了耦合度的问题,当然这是另外一个话题了。
笔者认为,软件设计的本质之一在于对耦合关系的优化处理,通常我们讲“高内聚、低耦合”的系统架构是值得学习和借鉴的(比如springframework中的IOC理论就极大的降低了体系内的耦合度)。
那么在软件设计中,是否有一个原则描述了耦合关系呢?
有的,那就是是 迪米特法则 , 下篇博客我们再展开聊聊耦合处理的二三事。


关注我,共同进步,每周至少一更。——Wayne

这篇关于05.开闭原则(Open Closed Principle)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/487821

相关文章

忽略某些文件 —— Git 学习笔记 05

忽略某些文件 忽略某些文件 通过.gitignore文件其他规则源如何选择规则源参考资料 对于某些文件,我们不希望把它们纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常它们都是些自动生成的文件,比如日志文件、编译过程中创建的临时文件等。 通过.gitignore文件 假设我们要忽略 lib.a 文件,那我们可以在 lib.a 所在目录下创建一个名为 .gi

JVM内存调优原则及几种JVM内存调优方法

JVM内存调优原则及几种JVM内存调优方法 1、堆大小设置。 2、回收器选择。   1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。   2、对JVM内存的系统级的调优主要的目的是减少

Open a folder or workspace... (File -> Open Folder)

问题:vscode Open with Live Server 时 显示Open a folder or workspace... (File -> Open Folder)报错 解决:不可以单独打开文件1.html ; 需要在文件夹里打开 像这样

android java.io.IOException: open failed: ENOENT (No such file or directory)-api23+权限受权

问题描述 在安卓上,清单明明已经受权了读写文件权限,但偏偏就是创建不了目录和文件 调用mkdirs()总是返回false. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.READ_E

[轻笔记] pip install : Read timed out. (closed)

添加超时参数(单位秒) pip --default-timeout=10000 install ${package_name}

C++入门(05-2)从命令行执行C++编译器_GCC

文章目录 GCC编译器1. 下载MinGW-w64,安装(不推荐)2. 使用MSYS2安装MinGW-w64(推荐)2.1 安装MSYS22.2 初始化和更新2.3 安装MinGW-w64编译器2.3 在MSYS2 Shell中导航到代码目录2.4 使用 g++ 编译2.5 运行可执行文件 GCC编译器 GCC(GNU Compiler Collection)是一个开源编译器集

Open-Sora代码详细解读(1):解读DiT结构

Diffusion Models专栏文章汇总:入门与实战 前言:目前开源的DiT视频生成模型不是很多,Open-Sora是开发者生态最好的一个,涵盖了DiT、时空DiT、3D VAE、Rectified Flow、因果卷积等Diffusion视频生成的经典知识点。本篇博客从Open-Sora的代码出发,深入解读背后的原理。 目录 DiT相比于Unet的关键改进点 Token化方

C++入门(05)从命令行执行C++编译器_MSVC

文章目录 1.C++ 编译器2. 常用 C++ 编译器MSVC(Microsoft Visual C++)GCC(GNU Compiler Collection)Clang 3. MSVC 编译器3.1 开发者命令提示符3.2 编译 C++ 代码 1.C++ 编译器 将C++源代码(扩展名为 .cpp )转换成计算机可以运行的可执行程序 编译器会检查代码的语法和语义,生成相应

龙芯+FreeRTOS+LVGL实战笔记(新)——05部署主按钮

本专栏是笔者另一个专栏《龙芯+RT-Thread+LVGL实战笔记》的姊妹篇,主要的区别在于实时操作系统的不同,章节的安排和任务的推进保持一致,并对源码做了改进和优化,各位可以先到本人主页下去浏览另一专栏的博客列表(目前已撰写36篇,图1所示),再决定是否订阅。此外,也可以前往本人在B站的视频合集(图2所示)观看所有演示视频,合集首个视频链接为: 借助RT-Thread和LVGL

error while loading shared libraries: libnuma.so.1: cannot open shared object file:

腾讯云CentOS,安装Mysql时: 1.yum remove libnuma.so.1 2.yum install numactl.x86_64