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

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

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

相关文章

D. In Love

贪心,维护最靠左的右端点以及最靠右的左端点 // Problem: D. In Love // Contest: Codeforces - Codeforces Round 905 (Div. 3) // URL: https://codeforces.com/contest/1883/problem/D // Memory Limit: 256 MB // Time Limit: 2000 ms // // Powered by CP Edi…

【从0配置JAVA项目相关环境1】jdk + VSCode运行java + mysql + Navicat + 数据库本地化 + 启动java项目

从0配置JAVA项目相关环境 写在最前面一、安装Java的jdk环境1. 下载jdk2. 配置jdk3. 配置环境变量 二、在vscode中配置java运行环境1. 下载VSCode2. 下载并运行「Java Extension Pack」 三、安装mysql1.官网下载MySQL2.开始安装如果没有跳过安装成功 3.配置MySQL Server4.环境变…

【爬虫】自动下载指定网站全部图片(Java版)

爬虫是一种自动化程序,能够模拟人类的浏览行为,访问网络资源并提取所需数据。它可以通过发送HTTP请求获取网页内容,并对网页进行解析和数据提取。 在大多数时候,提到爬虫我们就会想到 Python,其实 Java 也是可以实现爬…

判断完数(写出部分函数)

例如:本题要求实现一个函数,判断一个自然数是否是完数。如果一个自然数除自身之外的因子和等于它自己,则称该数为完数。例如 6 1 2 3;则6是完数。 函数接口定义: 在这里描述函数接口。:int isPerfect (…

ApplicationContextAware 类

优质博文:IT-BLOG-CN 需求: 使用autowired注入一些对象,但发现不可以直接使用Autowired,因为方法是static的,要使用该方法当前对象也必须是static,正常情况下Autowired无法注入静态的bean,于是…

数据结构与算法编程题44

有向无权图邻接矩阵表示 //参考博客&#xff1a;https://blog.csdn.net/qq_54162207/article/details/117414707#include <iostream> using namespace std;#define Maxsize 100 #define VertexmMaxNum 20 #define ERROR 0 #define OK 1 typedef string VertexType; …

img标签禁止右键点击复制图片等功能

场景描述&#xff1a;在网页中显示图片&#xff0c;但是不想让其他人右键保存图片 会用到一个新的属性&#xff1a;oncontextmenu 代码如下 <img src"./123.png" alt"" oncontextmenu"return false">这样虽然能解决不在当前页右键保存图片…

微信玩具小程序商城开发技巧

小程序已成为许多企业和个人开展业务的重要工具之一。如果你想在玩具行业中打造一个小程序商城&#xff0c;但又没有相关的编程经验&#xff0c;不用担心&#xff01;本文将通过乔拓云平台提供的简单操作步骤&#xff0c;分享给你玩具行业小程序平台搭建的教程。 首先&#xff…

DriveWorks——参数化设计非标定制利器

DriveWorks基本介绍 DriveWorks是一套被 SOLIDWORKS 认可为金牌合作伙伴产品的设计自动化软件。DriveWorks 可自动创建特定于订单的销售文档和 SOLIDWORKS 制造数据。减少重复性任务&#xff0c;消除错误&#xff0c;增加销售额&#xff0c;并在创纪录的时间内交付定制产品。 为…

Linux下的查看文件的命令

1. tail 命令 tail 命令是在 Linux 和类 Unix 系统上用来显示文件末尾内容的命令。它最常用于查看文件的末尾几行内容&#xff0c;通常在日志文件或其他不断更新的文件中使用。 以下是 tail 命令的常用选项和用法&#xff1a; 1.1. 基本用法 tail file_name这将默认显示文件…

python的异常处理批量执行网络设备的巡检命令

前言 在网络设备数量超过千台甚至上万台的大型企业网中&#xff0c;难免会遇到某些设备的管理IP地址不通&#xff0c;SSH连接失败的情况&#xff0c;设备数量越多&#xff0c;这种情况发生的概率越高。 这个时候如果你想用python批量配置所有的设备&#xff0c;就一定要注意这…

top K问题(C语言)

目录 前言 top K问题 模拟数据 建堆 验证&#xff08;简单了解即可&#xff09; 最终代码 调试部分 前言 在大小堆的实现&#xff08;C语言&#xff09;中我们讨论了堆的实际意义&#xff0c;在看了就会的堆排序&#xff08;C语言&#xff09;中我们完成了堆排序&#…

Java利用UDP实现简单的双人聊天

一、创建新项目 首先创建一个新的项目&#xff0c;并命名。 二、实现代码 import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.net.*; import java.io.IOException; import java.lang.String; public class liaotian extends JFrame{ pri…

找丢失号码(桶排序)

题目&#xff1a;小明拿着本院100名校运会选手的号码牌走向操场&#xff0c;号码是101到200.出于道路施工加上下雨&#xff0c;小明摔跤后将号码牌散落在地&#xff0c;小明捡起来发现只有99张号码牌&#xff0c;请你编程帮小明快速找到是什么号码丢失 #include <stdio.h>…

【13】PyQt多线程多任务管理

目录 多线程&多任务介绍 多线程管理 1. 拷贝依赖 2. 使用示例 多任务管理 1. 拷贝依赖 2. 使用示例 多线程&多任务介绍 多线程&多任务通常是指将一个任务或多个任务运行在子线程&#xff0c;并且子线程可以独立启动&#xff0c;或通过线程池启动。子线程通…

深度探索 Python Pyramid 框架

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com Pyramid是一个灵活且强大的Python web框架&#xff0c;广泛用于构建各种规模的Web应用程序。本文将深度探索Pyramid框架&#xff0c;介绍其核心概念、应用场景以及一些高级特性。 安装与基础用法 首先&#xf…

JS学习--类型转换

函数转换 parseInt() 转换之前&#xff0c;首先会分析该字符串。判断位置为0处的字符串&#xff0c;判断是否为有效数字&#xff0c;若否&#xff0c;直接返回NaN&#xff0c;不再继续&#xff1b; 若是&#xff0c;继续打印直到不为数字的地方停止 parseFloat() 转换之前&…

linux日志优先级

7种日志级别代号0-7 0 debug #有调试信息的&#xff0c;日志信息最多 1 info #一般信息的日志&#xff0c;最常用 2 notice #最具有重要性的普通条件的信息 常见 3 warning #警告级别 常见 4 …

探索鸿蒙 DevEcoStudio汉化+运行报错

在下载好软件&#xff0c;摸索着成功创建了一个项目的时候&#xff0c;点击运行&#xff0c;竟然失败了。而且一大堆的英文也不知道从何入手&#xff0c;从网上搜了一下&#xff0c;找到了汉化的办法&#xff0c;并且解决了问题。我这里走的是Mac的步骤&#xff0c;微软的其实一…

ReadWriteLock 和 StampedLock 的比较与解析

在多线程编程中&#xff0c;我们经常需要使用锁来保证同一时刻只有一个线程能够访问共享资源。Java提供了多种锁的实现&#xff0c;如ReentrantLock、ReadWriteLock、StampedLock等。本文将对ReadWriteLock和StampedLock进行比较&#xff0c;分析它们的原理、优缺点&#xff0c…