当前位置: 首页 > news >正文

【23种设计模式】开闭原则

个人主页:金鳞踏雨

个人简介:大家好,我是金鳞,一个初出茅庐的Java小白

目前状况:22届普通本科毕业生,几经波折了,现在任职于一家国内大型知名日化公司,从事Java开发工作

我的博客:这里是CSDN,是我学习技术,总结知识的地方。希望和各位大佬交流,共同进步 ~

本文来自抖音《IT楠老师》设计模式课程,下面是本人结合原课件的一些学习心得。

一、原理概述

开闭原则(Open Closed Principle),简写为 OCP。软件实体(模块、类、方法等)应该"对扩展开放、对修改关闭"。一个很明显的例子就是——策略设计模式

对扩展开放、对修改关闭。

当我们需要添加一个新的功能时,应该在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。

开闭原则并不是不让我们修改代码,而是让我们尽量避免"大修大改",设计模式是方法,是套路,而不是"枷锁"!!!

案例分析

问题代码

以下是一个常见的生产环境中的例子,我们将展示一个简化的电商平台的订单折扣策略。下述代码是否可以符合开闭原则的定义呢?

class Order {private double totalAmount;public Order(double totalAmount) {this.totalAmount = totalAmount;}// 计算折扣后的金额public double getDiscountedAmount(String discountType) {double discountedAmount = totalAmount;if (discountType.equals("FESTIVAL")) {discountedAmount = totalAmount * 0.9; // 节日折扣,9折} else if (discountType.equals("SEASONAL")) {discountedAmount = totalAmount * 0.8; // 季节折扣,8折}return discountedAmount;}
}

上述代码中,Order 类包含一个计算折扣金额的方法,它根据不同的折扣类型应用折扣。当我们需要添加新的折扣类型时,就不得不需要修改 getDiscountedAmount() 方法的代码,这显然是不合理的,这就违反了开闭原则

优化

抽象接口

// 抽象折扣策略接口
interface DiscountStrategy {double getDiscountedAmount(double totalAmount);
}

具体策略

// 节日折扣策略
class FestivalDiscountStrategy implements DiscountStrategy {@Overridepublic double getDiscountedAmount(double totalAmount) {return totalAmount * 0.9; // 9折}
}// 季节折扣策略
class SeasonalDiscountStrategy implements DiscountStrategy {@Overridepublic double getDiscountedAmount(double totalAmount) {return totalAmount * 0.8; // 8折}
}
class Order {private double totalAmount;private DiscountStrategy discountStrategy;public Order(double totalAmount, DiscountStrategy discountStrategy) {this.totalAmount = totalAmount;this.discountStrategy = discountStrategy;}public void setDiscountStrategy(DiscountStrategy discountStrategy) {this.discountStrategy = discountStrategy;}// 计算折扣后的金额public double getDiscountedAmount() {return discountStrategy.getDiscountedAmount(totalAmount);}
}

在遵循开闭原则的代码中,我们定义了一个抽象的折扣策略接口 DiscountStrategy,然后为每种折扣类型创建了一个实现该接口的策略类。Order 类使用组合的方式,包含一个 DiscountStrategy 类型的成员变量,以便在运行时设置或更改折扣策略,(可以通过编码,配置、依赖注入等形式)。

这样,当我们需要添加新的折扣类型时,只需实现 DiscountStrategy 接口即可,而无需修改现有的 Order 代码。这个例子遵循了开闭原则。

二、修改代码就意味着违背开闭原则吗?

开闭原则的核心思想是要尽量减少对现有代码的修改,以降低修改带来的风险和影响。在实际开发过程中,完全不修改代码是不现实的!当需求变更或者发现代码中的错误时,修改代码是正常的。然而,开闭原则鼓励我们通过设计更好的代码结构,使得在添加新功能或者扩展系统时,尽量减少对现有代码的修改。

案例分析

以下是一个简化的日志记录器的示例,展示了在适当情况下修改代码,也不违背开闭原则。

在这个例子中,我们的应用程序支持将日志输出到控制台和文件。假设我们需要添加一个新功能,以便在输出日志时同时添加一个时间戳

原始代码

interface Logger {void log(String message);
}class ConsoleLogger implements Logger {@Overridepublic void log(String message) {System.out.println("Console: " + message);}
}class FileLogger implements Logger {@Overridepublic void log(String message) {System.out.println("File: " + message);// 将日志写入文件的实现省略}
}

为了添加时间戳功能,我们需要修改现有的 ConsoleLogger 和 FileLogger 类。虽然我们需要修改代码,但由于这是对现有功能的改进,而不是添加新的功能,所以这种修改是可以接受的,不违背开闭原则。

修改后的代码

interface Logger {void log(String message);
}class ConsoleLogger implements Logger {@Overridepublic void log(String message) {String timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);System.out.println("Console [" + timestamp + "]: " + message);}
}class FileLogger implements Logger {@Overridepublic void log(String message) {String timestamp = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);String logMessage = "File [" + timestamp + "]: " + message;System.out.println(logMessage);// 将日志写入文件的实现省略}
}

在这个例子中,我们只是对现有的日志记录器类进行了适当的修改,以添加时间戳功能。这种修改不会影响到其他部分的代码,因此不违背开闭原则。总之,适当的修改代码并不一定违背开闭原则,关键在于我们如何权衡修改的影响和代码设计。

三、如何做到 "对扩展开放、修改关闭"?

开闭原则讲的就是代码的扩展性问题,是判断一段代码是否易扩展的“黄金标准”。

如果某段代码在应对未来需求变化的时候,能够做到“对扩展开放、对修改关闭”,那就说明这段代码的扩展性比较好。

在讲具体的方法论之前,我们先来看一些更加偏向顶层的指导思想。为了尽量写出扩展性好的代码,我们要时刻具备扩展意识、抽象意识、封装意识。这些“潜意识”可能比任何开发技巧都重要。

有些时候,我们有必要思考如下问题:

  • 我要写的这段代码未来可能有哪些需求变更、如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,不需要改动代码整体结构、做到最小代码改动的情况下,新的代码能够很灵活地插入到扩展点上,做到“对扩展开放、对修改关闭”。
  • 我们还要识别出代码可变部分和不可变部分,要将可变部分封装起来,隔离变化,提供抽象化的不可变接口,给上层系统使用。当具体的实现发生变化的时候,我们只需要基于相同的抽象接口,扩展一个新的实现,替换掉老的实现即可,上游系统的代码几乎不需要修改。

需要注意的是,遵循开闭原则并不意味着永远不能修改代码。在实际开发过程中,完全不修改代码是不现实的。开闭原则的目标是要尽量降低修改代码带来的风险和影响,提高代码的可维护性和可复用性。在实际开发中,我们应该根据项目需求和预期的变化来平衡遵循开闭原则的程度。

文章到这里就结束了,如果有什么疑问的地方,可以在评论区指出~

希望能和大佬们一起努力,诸君顶峰相见

再次感谢各位小伙伴儿们的支持!!!

相关文章:

  • 深度学习面试题目01
  • Interview of ING internship for master thesis: LLM
  • 多测师肖sir_高级金牌讲师__接口测试之练习题(6.1)
  • 【广州华锐互动】VR公司工厂消防逃生演练带来沉浸式的互动体验
  • 【c++】打家劫舍(动态规划)
  • QWidget|QFrame设置背景透明且可以带有边框颜色
  • Vue(uniapp)父组件方法和子组件方法执行优先顺序
  • MacOS环境变量source生效但重启后又失效
  • Java学习星球,Java学习路线
  • LeetCode:20. 有效的括号——栈和队列
  • 企业引用CRM管理系统软件有什么作用?
  • 在U盘上运行的 Windows
  • Java设计模式(九)—— 中介者模式
  • HTML5支持的视频文件格式和音频文件格式有哪些?
  • 【图神经网络】10分钟掌握图神经网络及其经典模型
  • [case10]使用RSQL实现端到端的动态查询
  • 《剑指offer》分解让复杂问题更简单
  • 【399天】跃迁之路——程序员高效学习方法论探索系列(实验阶段156-2018.03.11)...
  • 【刷算法】求1+2+3+...+n
  • Angular2开发踩坑系列-生产环境编译
  • Create React App 使用
  • Gradle 5.0 正式版发布
  • jquery cookie
  • mysql 数据库四种事务隔离级别
  • MySQL的数据类型
  • nginx 配置多 域名 + 多 https
  • node入门
  • PHP变量
  • spring学习第二天
  • thinkphp5.1 easywechat4 微信第三方开放平台
  • 前端面试之闭包
  • 微信开源mars源码分析1—上层samples分析
  • ​软考-高级-系统架构设计师教程(清华第2版)【第9章 软件可靠性基础知识(P320~344)-思维导图】​
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • (10)ATF MMU转换表
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (AtCoder Beginner Contest 340) -- F - S = 1 -- 题解
  • (zhuan) 一些RL的文献(及笔记)
  • (黑马出品_高级篇_01)SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式
  • (六)激光线扫描-三维重建
  • (六)什么是Vite——热更新时vite、webpack做了什么
  • (论文阅读30/100)Convolutional Pose Machines
  • (转)nsfocus-绿盟科技笔试题目
  • (转)编辑寄语:因为爱心,所以美丽
  • *setTimeout实现text输入在用户停顿时才调用事件!*
  • .360、.halo勒索病毒的最新威胁:如何恢复您的数据?
  • .bat批处理(八):各种形式的变量%0、%i、%%i、var、%var%、!var!的含义和区别
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .NET : 在VS2008中计算代码度量值
  • .NET MVC之AOP
  • .NET 简介:跨平台、开源、高性能的开发平台
  • .net通用权限框架B/S (三)--MODEL层(2)
  • @ 代码随想录算法训练营第8周(C语言)|Day57(动态规划)
  • @SuppressWarnings(unchecked)代码的作用
  • [ 代码审计篇 ] 代码审计案例详解(一) SQL注入代码审计案例