设计原则-开闭原则

news2024/11/28 20:34:19

世界上没有任何一个项目是不需要迭代的,随着项目的发展壮大,会有越来越多的功能代码会被修改、添加、删除。据统计线上的生产事故90%都有由于变更引起的,因此为保证项目的迭代稳定性,我们需尽可能的遵守开闭原则。那开闭原则到底是什么?开和闭如何矛盾而统一呢?实际开发中该原则是否可执行?又该如何应用呢?

一、开闭原则概念

开闭原则指的是一个软件实体应该通过扩展来实现变化,而不是通过修改已有代码来实现变化,即对扩展开放,对修改关闭。开闭原则主要考虑了项目的未来事件而制定的对现行开发进行约束的原则。这个原则对于实际开发认知观念的改变十分有用,需求并不是实现就行,并不是能跑通即可,必须要能够适应未来的发展变化。
根据原则定义,针对扩展开放,修改关闭是否就意味着我们完全不能够修改代码,只能够添加代码?具体比如在一个类中增加一个方法,难道不是意味着修改了这个类?到底什么样的项目变动可以被定义为扩展,什么样的项目变动可以被定义为修改?
开闭原则的本质就是为了适应未来变化而保证项目的稳定迭代,因此,可以说只要不影响现有功能的改动都是扩展,凡是影响现有功能的改动都是修改。开闭原则要求未来可能发生的改动不会影响到现有代码。回过头看,在类中增加方法,而不是复用原有方法影响原有业务,这就是扩展,是符合开闭原则的。

二、未来变动范围

前面说了,开闭原则主要考虑了项目的未来事件而制定的对现行开发进行约束的原则。为了避免过度设计,我们也需要说清楚未来变化的可能程度。就如用户信息距离,在用户权限管理项目中,我们可能需要细化用户与权限的关系,权限存在很多种,每个用户均可能存在多个权限。如果设计初始化方案如下:
在这里插入图片描述
如上图(a)所示,如果后续业务添加权限3,此时需要改动用户1、用户2的权限信息,非常不利于后续维护,甚至会对之前的权限信息产生影响。而后一种方案(b)将各种权限进行分组为角色,用户拥有不同角色,再根据角色关联其拥有的权限。后续添加权限3,只需要增加角色下的权限即可,不会影响用户的其他角色的权限信息。
从上看,针对于用户信息中的权限信息可以考虑通过引入角色来尽可能减少未来变化对已有功能的影响,其实,用户信息中的联系方式、姓名等信息,如果仔细思考也有可能在未来发生改变,如不同种类的联系方式、不同格式的姓名等。所以,我们需要根据自己的具体业务来判断是否需要考虑增加用户某信息后,未来是否可能发生改变。

三、如何做到?

理解了开闭原则概念之后,我们就必须要知道如何做才能尽可能满足开闭原则。这一点非常难,难是因为大部分都没有对此真正的思考过。要说明的是,并不是你使用了哪种设计模式或遵循了各种设计原则,所以你的代码就满足了开闭原则。前面一小节避免开闭原则的过度设计就是要理解当下具体业务。而要满足开闭原则设计,我认为也应当从当下具体业务出发,分解业务需求,根据业务理解把握未来变化的提前设计。下面我们通过具体的案例,来体会下实际开发过程中应当如何满足开闭原则。
假设目前需要开发一个根据系统各种指标监控以及告警规则来向运维同学发出告警的小工具。

3.1 初步方案

初步方案不考虑设计原则,也不考虑代码可扩展性,目的仅是实现功能即可。设计类图及核心相关代码如下所示:
在这里大撒大撒图片大萨达

public class Client {
    private final Alarm alarm = new Alarm();
    private final AlarmRule rule = new AlarmRule();
    public Client() {rule.setQpsLimiter(100);}
    @Test
    public void main() {
        double serverQps = 112.5;
        alarm.doAlarm(serverQps, rule);
    }
}
public class Alarm {
    private final TelePhone telePhone = new TelePhone();
    public void doAlarm(double qps, AlarmRule rule) {
        if(qps > rule.getQpsLimiter()) {
            telePhone.call("触发qps阈值告警!!");
        }
    }
}

目前已经能够实现通过系统qps指标来进行系统监控报警的功能了,但是这样的代码是否满足开闭原则值得我们思考一下。未来如果需要增加系统其他指标以及其他告警规则,这种代码如何修改呢?① 修改alarm的doAram方法,方法入参和内部逻辑均需要修改。并且修改入参这个动作明显会影响到原有功能。② AlarmRule类需要修改,增加其他规则,以及判断规则的方法。很显然仔细分析这种设计代码的方式并不满足开闭原则。

3.2 正确方案

为了满足开闭原则的设计,我们需要分析需求本身。需求的目的是为了开发一个根据系统各种监控指标以及一系列的告警规则来发送告警的工具。首先我们要分析需求本身具备哪些设计对象。

  • 监控指标
    存在多个,并且后续可能存在其他扩展
  • 告警规则
    存在多个,不同规则判断逻辑不同
  • 发送告警手段
    可能存在多个方式去发送告警,如电话、邮件等等。不同方式之间发送的要求不同
    然后就是不同设计对象之间的交互问题,如告警规则需依赖监控指标,监控指标、告警手段应都只又Client进行感知即可。设计类图及核心相关代码如下所示:
    在这里插入图片描述
public class Client {
    private final UserInfo userInfo;
    private final Notice alarmMethod;
    private final List<Rule> ruleList = new ArrayList<>();

    public Client() {
        userInfo = new UserInfo()
                .setName("张三")
                .setTelePhone("110")
                .setEmailInfo("zs@110.com");
        alarmMethod = new TelePhoneNotice();

        ruleList.add(new QpsLimiterRule(100));
        ruleList.add(new ResponseTimeRule(20, 300));
    }

    @Test
    public void main() {
        Monitor monitor = new Monitor()
                .setQps(110.2)
                .setAverageTime(35)
                .setMaxTime(203);
        for (Rule rule : ruleList) {
            if (rule.checkMonitor(monitor)) {
                alarmMethod.sendMsg(userInfo, rule.alarmMsg());
                return;
            }
        }
    }
}

相比于之前的设计方案,这种代码组织设计更优。以后即使增加监控指标以及告警规则,在此代码基础上的改动也不会影响到原有规则判断逻辑,只需要在Monitor类中添加监控指标,添加新的规则Rule实现类。每个告警规则都有独自的规则判断逻辑,以及不满足规则时告警文案。

3.3 小结

总结下实现开闭原则的大致步骤:

  1. 分析需求目标
  2. 拆分实现需求目标的各个设计元素,设计元素的含义、特点以及后续是否存在改动。
  3. 分析各设计元素之间的业务依赖关系
  4. 分析整体设计对代码扩展性、稳定性、可维护性
    最后还有一点,避免过度设计,如上例子中,我们并没有对监控指标进行分类拆分,也没有对用户信息进行过度设计,因为这些都不是需求目标(业务)的重点,这种告警工具以后会需要用户权限管理吗?不太可能需求。【但是可能需要用户分组告警,即使如此,这个改动也是在客户端Client中修改,不影响原功能逻辑】

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

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

相关文章

Jeston Orin Nano Sdkmanager 自动化安装部署官网CUAD环境

大家好&#xff0c;我是虎哥&#xff0c;入手一块Jeston Orin nano 8G模块&#xff0c;这个模块因为是英伟达未来5年左右主推的模块&#xff0c;所以我逐步会将之前所有的应用都在这个模块环境上做适配&#xff0c;本章内容&#xff0c;我将主要围绕烧写安装系统后&#xff0c;…

R7-13 小明找前缀100000(假)

题目背景 小明最近上课天天睡觉&#xff0c;于是啥都不会。 一天&#xff0c;老师终于点兵点将点到他回答问题&#xff0c;你能帮他渡过难关吗&#xff1f; 现在老师给了小明 n 个由 0、1 构成的字符串&#xff0c;然后有 m 次询问&#xff0c; 每次询问给出一个由 0、1 构…

关于Vue3 ,看这一篇文档你就会用了

随着Vue3的到来&#xff0c;公司的新项目全部进行了升级&#xff0c;相比于Vue2&#xff0c;语法上个人觉得更简洁&#xff0c;更容易通俗易懂。首先安装vue3项目&#xff0c;这里我使用vite进行安装&#xff08;强烈推荐&#xff0c;启动速度贼快&#xff09; npm create vit…

Android 12.0状态栏居中显示时间和修改时间显示样式

1.概述 在12.0的系统rom定制化开发中,在systemui状态栏系统时间默认显示在左边和通知显示在一起,但是客户想修改显示位置,想显示在中间,所以就要修改SystemUI 的Clock.java 文件这个就是管理显示时间的,居中显示的话就得修改布局文件了 效果图如下: 在这里插入图片描述 …

算法基础学习笔记——⑩DFS与BFS\树与图

✨博主&#xff1a;命运之光 ✨专栏&#xff1a;算法基础学习 目录 DFS与BFS\树与图 ✨DFS ✨BFS &#x1f353;宽搜流程图如下&#xff1a; &#x1f353;宽搜流程&#xff1a; &#x1f353;广搜模板 ✨树与图 &#x1f353;树是特殊的图&#xff08;连通无环的图&am…

第09讲:SkyWalking Agent 启动流程剖析,领略微内核架构之美

微内核架构 SkyWalking Agent 采用了微内核架构&#xff08;Microkernel Architecture&#xff09;&#xff0c;那什么是微内核架构呢&#xff1f;微内核架构也被称为插件化架构&#xff08;Plug-in Architecture&#xff09;&#xff0c;是一种面向功能进行拆分的可扩展性架构…

英文论文(sci)解读复现【NO.8】基于注意机制和感受野的YOLOv5在唐卡图像缺陷识别中的应用

此前出了目标检测算法改进专栏&#xff0c;但是对于应用于什么场景&#xff0c;需要什么改进方法对应与自己的应用场景有效果&#xff0c;并且多少改进点能发什么水平的文章&#xff0c;为解决大家的困惑&#xff0c;此系列文章旨在给大家解读发表高水平学术期刊中的 SCI论文&a…

【Unity100个实用小技巧】世界Canvas自动隐藏,包含子物体

☀️博客主页&#xff1a;CSDN博客主页&#x1f4a8;本文由 萌萌的小木屋 原创&#xff0c;首发于 CSDN&#x1f4a2;&#x1f525;学习专栏推荐&#xff1a;面试汇总❗️游戏框架专栏推荐&#xff1a;游戏实用框架专栏⛅️点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd;&#…

【JavaEE】JUC(java.util.concurrent)的常见类以及线程安全的集合类

目录 1、JUC&#xff08;java.util.concurrent&#xff09;的常见类 1.1、Callable接口的用法&#xff08;创建线程的一种写法&#xff09; 1.2、ReentrantLock可重入互斥锁 1.2.1、ReentrantLock和synchronized的区别 1.2.2、如何选择使用哪个锁 1.3、Semaphore信号量 1…

pta(浙大第四版)五道经典练习题③

目录 ①7-4 IP地址转换 ②、查找日期 ③藏头词 四、IP地址转换 五、删除链表值为偶数的节点 ①7-4 IP地址转换 题述&#xff1a;IP地址转换&#xff1a;一个IP地址是用四个字节&#xff08;每个字节8个位&#xff09;的二进制码组成。输入32位二进制字符串&#xff0c;输…

探索iOS转场动画

iOS提供图像转场动画&#xff0c;可实现酷炫的转场特效。动画包括&#xff1a;溶解、折叠、复印机、暴露、翻页、波纹、滑动等等。 一、溶解动画 CIDissolveTransition提供溶解动画&#xff0c;我们来看看对应的转场动画效果&#xff1a; 在CIFilter指定CIDissolveTransition…

Qt线程基础,多线程使用注意点,目前支持的线程种类。

Qt线程基础 一、什么是线程&#xff1f;二、GUI线程和工作线程三、同时访问数据四、使用线程1、何时使用线程的替代品2、应该用哪种Qt线程技术&#xff1f; 六、Qt中的多线程技术1、QThread:带有可选事件循环的低级API2、QThreadPool和QRunnable:重用线程 七、Qt Concurrent:使…

集成学习以及随机森林介绍

一、集成学习简介 1.什么是集成学习&#xff1f; 集成学习&#xff08;Ensemble Learning&#xff09;是一种机器学习方法&#xff0c;通过将多个弱学习器&#xff08;weak learner&#xff09;组合在一起来构建一个更强大的学习器&#xff08;strong learner&#xff09;。 …

C语言进阶——字符函数和字符串函数(下)

在前面我们已经学习了strlen、strcpy、strcat、strcmp几个库函数&#xff0c;今天我们继续学习剩余的库函数。 上期链接&#xff1a; C语言进阶——字符函数和字符串函数&#xff08;上&#xff09;_wangjiushun的博客-CSDN博客 目录&#xff1a; 3、长度受限制的字符串函数…

Redis(四)持久化策略

文章目录 持久化策略1、为什么Redis需要持久化2、Redis提供的两种持久化方式(1)RGB持久化详解概述RGB持久化的两种触发策略手动触发实例测试&#xff1a;自动触发实例测试&#xff1a; 查看rdb的状态信息info Persistence rdb模式的优缺点 (2)AOF持久化详解AOF持久化步骤&#…

近期复盘 | 想多了都是问题,想开了都是答案

文章目录 &#x1f339;四月坚持背单词&#xff0c;五月坚持利用AI写文章&#x1f60a;六月会坚持干什么&#x1f64c;23年7月&#xff1a;毕业两年&#xff0c;参保两年&#x1f440;强制存储&#xff0c;消费降级&#xff0c;开源节流&#x1f61c;好好深耕能力&#x1f381;…

JavaScript 进阶 (一)

目录 作用域 局部作用域 函数作用域 块作用域 全局作用域 作用域链 JS垃圾回收机制 闭包 变量提升 函数进阶 函数提升 函数参数 箭头函数 基本语法 箭头函数参数 箭头函数this 解构赋值 数组解构 对象解构 遍历数组 forEach 方法&#xff08;重点&#xff09; …

shell SNAT与DNAT

文章目录 SNATSNAT原理与应用SNAT实验 DNATDNAT原理与应用DNAT实验 SNAT SNAT原理与应用 SNAT 应用环境&#xff1a;局域网主机共享单个公网IP地址接入Internet&#xff08;私有不能早Internet中正常路由&#xff09; SNAT原理&#xff1a;修改数据包的源地址。 SNAT转换前提…

文心一言 VS 讯飞星火 VS chatgpt (23)-- 算法导论4.2 5题

五、V.Pan 发现一种方法&#xff0c;可以用 132 464 次乘法操作完成 68 x 68 的矩阵相乘&#xff0c;发现另一种方法&#xff0c;可以用 143 640 次乘法操作完成 70 x 70 的矩阵相乘&#xff0c;还发现一种方法&#xff0c;可以用155 424次乘法操作完成 72 x 72 的矩阵相乘。当…

数据安全治理科技产品能力-数据安全复合治理框架和模型解读(2)

数据治理,数据安全治理行业在发展,在实践,所以很多东西是实践出来的,哪有什么神仙理论指导,即使有也是一家之说,但为了提高企业投产比,必要的认知是必须的,落地数据安全治理科技水平差异直接决定产品和项目是否可持续性,当前和未来更需要专业和有效创新。数据安全治理…