设计模式学习之开闭原则

news/2024/5/17 14:03:39

学习内容均来自抖音号 【it楠老师教java】课程。

1、原理概述

开闭原则的英文全称是 Open Closed Principle,简写为 OCP。它的英文描述是:software entities (modules, classes, functions, etc.) should be open for extension , but closed for modification。我们把它翻译成中文就是:软件实体(模块、类、方法等)应该“对扩展开放、对修改关闭”。说人话就是,当我们需要添加一个新的功能时,应该在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。以下是一个常见的生产环境中的例子,我们将展示一个简化的电商平台的订单折扣策略。
你觉得下边的代码有问题吗?

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 {
@Override
public double getDiscountedAmount ( double totalAmount ) {
return totalAmount * 0.9 ; // 9
}
}
// 季节折扣策略
class SeasonalDiscountStrategy implements DiscountStrategy {
@Override
public 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 ; }

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

这里相信很多初级程序员看到都会有种恍然大悟的感觉,或者某些中级程序员不注重代码风格的人也会有所顿悟,csdn上写设计模式的人有一大堆,但是没人告诉你怎么用,这个写的确实不错,都是写程序的人了,十几二十块真不算什么,这个老师还是有点水平的。

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

开闭原则的核心思想是要尽量减少对现有代码的修改,以降低修改带来的风险和影 响。在实际开发过程中,完全不修改代码是不现实的。当需求变更或者发现代码中的 错误时,修改代码是正常的。然而,开闭原则鼓励我们通过设计更好的代码结构,使 得在添加新功能或者扩展系统时,尽量减少对现有代码的修改。 以下是一个简化的日志记录器的示例,展示了在适当情况下修改代码,也不违背开闭 原则。在这个例子中,我们的应用程序支持将日志输出到控制台和文件。假设我们需 要添加一个新功能,以便在输出日志时同时添加一个时间戳。

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

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

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

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

当我们遵循开闭原则时,其目的是为了让我们的代码更容易维护、更具可复用性,同时降低了引入新缺陷的风险。但是,在某些情况下,遵循开闭原则可能会导致过度设计,增加代码的复杂性。因此,在实际开发中,我们应该根据实际需求和预期的变化来平衡遵循开闭原则的程度。写代码不是为了设计而设计,脱离需求谈设计都是耍流氓,有些场景,比如项目的使用频率不高,修改的可能性很低,或者代码本来就很简单使用了设计模式可能会增加开发难度,提升开发成本,反而得不偿失。


http://www.mrgr.cn/p/57741886

相关文章

2023 蓝桥杯真题B组 C/C++

https://www.dotcpp.com/oj/train/1089/ 题目 3150: 蓝桥杯2023年第十四届省赛真题-冶炼金属 题目描述 小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。这个炉子有一个称作转换率的属性 V,V 是一个正整数,这意味着消耗 V 个普通金 属 O…

react-native 输入框 被软键盘遮挡 (KeyboardAvoidingView)

本组件用于解决一个常见的尴尬问题&#xff1a;手机上弹出的键盘常常会挡住当前的视图。本组件可以自动根据键盘的高度&#xff0c;调整自身的 height 或底部的 padding&#xff0c;以避免被遮挡。 <KeyboardAvoidingViewbehavior{Platform.OS ios ? padding : height}key…

【点云处理教程】02从 Python 中的深度图像估计点云

一、说明 这是“点云处理”教程的第二篇文章。“点云处理”教程对初学者友好&#xff0c;我们将在其中简单地介绍从数据准备到数据分割和分类的点云处理管道。在本教程中&#xff0c;我们将学习如何在不使用 Open3D 库的情况下从深度图像计算点云。我们还将展示如何优化代码以获…

【VUE】解决图片视频加载缓慢/首屏加载白屏的问题

1 问题描述 在 Vue3 项目中&#xff0c;有时候会出现图片视频加载缓慢、首屏加载白屏的问题 2 原因分析 通常是由以下原因导致的&#xff1a; 图片或视频格式不当&#xff1a;如果图片或视频格式选择不当&#xff0c;比如选择了无损压缩格式&#xff0c;可能会导致文件大小过大…

微信小程序交易体验分常见问题指引

小程序交易体验分是为保障小程序用户的交易体验&#xff0c;促进开发者向用户提供更好的服务&#xff0c;帮助开发者更好的评估自身服务水平的机制。平台将对开发者在其小程序的违规行为进行判定&#xff0c;根据违规行为的严重程度对该小程序扣减不同分值的交易体验分&#xf…

风靡朋友圈的妙鸭相机,到底用了哪些底层技术?

不知道大家近期的朋友圈有没有被和海马体、天真蓝如出一辙的AI写真刷屏&#xff01; 这些面若桃花、精致到头发丝、光影充满氛围感的写真都是一款叫“妙鸭相机”的小程序生成的&#xff01;只要9.9&#xff0c;就能体验999写真&#xff01; 虽然只要9.9&#xff0c;但生成的照片…

opencv-24 图像几何变换03-仿射-cv2.warpAffine()

什么是仿射&#xff1f; 仿射变换是指图像可以通过一系列的几何变换来实现平移、旋转等多种操作。该变换能够 保持图像的平直性和平行性。平直性是指图像经过仿射变换后&#xff0c;直线仍然是直线&#xff1b;平行性是指 图像在完成仿射变换后&#xff0c;平行线仍然是平行线。…

海康摄像头开发笔记(一):连接防爆摄像头、配置摄像头网段、设置rtsp码流、播放rtsp流、获取rtsp流、调优rtsp流播放延迟以及录像存储

文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/131679108 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结…

服务器 Docker Alist挂载到本地磁盘(Mac版)夸克网盘

1.服务器下载alist 默认有docker环境 docker pull xhofe/alist2.生成容器 -v /home/alist:/opt/alist/data 这段意思是alist中的数据映射到docker 主机的文件夹&#xff0c;/home/alist就是我主机的文件夹&#xff0c;这个文件夹必须先创建 docker run -d --restartalways…

【Python】数据分析+数据挖掘——探索Pandas中的数据筛选

1. 前言 当涉及数据处理和分析时&#xff0c;Pandas是Python编程语言中最强大、灵活且广泛使用的工具之一。Pandas提供了丰富的功能和方法&#xff0c;使得数据的选择、筛选和处理变得简单而高效。在本博客中&#xff0c;我们将重点介绍Pandas中数据筛选的关键知识点&#xff…

ChatGPT结合知识图谱构建医疗问答应用 (一) - 构建知识图谱

一、ChatGPT结合知识图谱 在本专栏的前面文章中构建 ChatGPT 本地知识库问答应用&#xff0c;都是基于词向量检索 Embedding 嵌入的方式实现的&#xff0c;在传统的问答领域中&#xff0c;一般知识源采用知识图谱来进行构建&#xff0c;但基于知识图谱的问答对于自然语言的处理…

Nginx配置WebSocket反向代理

1、WebSocket协议 ​ WebSocket协议相比较于HTTP协议成功握手后可以多次进行通讯&#xff0c;直到连接被关闭。但是WebSocket中的握手和HTTP中的握手兼容&#xff0c;它使用HTTP中的Upgrade协议头将连接从HTTP升级到WebSocket。这使得WebSocket程序可以更容易的使用现已存在的…

深度学习——LSTM解决分类问题

RNN基本介绍 概述 循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;是一种深度学习模型&#xff0c;主要用于处理序列数据&#xff0c;如文本、语音、时间序列等具有时序关系的数据。 核心思想 RNN的关键思想是引入了循环结构&#xff0c;允许…

记一次安装nvm切换node.js版本实例详解

最后效果如下&#xff1a; 背景&#xff1a;由于我以前安装过node.js&#xff0c;后续想安装nvm将node.js管理起来。 问题&#xff1a;nvm-use命令行运行成功&#xff0c;但是nvm-list显示并没有成功。 原因&#xff1a;因为安装过node.js&#xff0c;所以原先的node.js不收n…

CloudStudio搭建Next框架博客_抛开电脑性能在云端编程(沉浸式体验)

文章目录 ⭐前言⭐进入cloud studio工作区指引&#x1f496; 注册coding账号&#x1f496; 选择cloud studio&#x1f496; cloud studio选择next.js&#x1f496; 安装react的ui框架&#xff08;tDesign&#xff09;&#x1f496; 安装axios&#x1f496; 代理请求跨域&#x…

2.获取DOM元素

获取DOM元素就是利用JS选择页面中的标签元素 2.1 根据CSS选择器来获取DOM元素(重点) 2.1.1选择匹配的第一个元素 语法: document.querySelector( css选择器 )参数: 包含一个或多个有效的CSS选择器 字符串 返回值: CSS选择器匹配的第一个元素&#xff0c;一个HTMLElement对象…

Data Structure, Algorithm,and Applications in C++

在学习这本书进阶内容之前&#xff0c;我们可以跟着它的第一章部分再巩固和复习。本书由Sartaj Sahni撰写&#xff0c;由王立柱和刘志红翻译。全书通俗易懂&#xff0c;内容丰富&#xff0c;是巩固C内容的不二选择。希望本文对各位有所帮助。 目录 1.函数与参数 1.1.传值参数…

【Linux】用户相关内容

如果命令ll 出现以上信息&#xff0c;UID为具体的数字&#xff0c;代表之前UID为502的用户被删除了。 更改目录或文件所属用户和所属组 在Linux中&#xff0c;创建一个文件时&#xff0c;该文件的拥有者都是创建该文件的用户。 更改所属用户 chown 用户名 文件名/目录名 更…

:is()、:where() 和 :has() 伪元素

:is()、:where() 和 :has() 伪元素是 CSS 中用于样式化元素的非常强大的工具。它们是在 CSS 选择器 Level 4 规范中引入的。它们允许我们将样式应用于符合特定条件的任何元素&#xff0c;例如元素的类型、元素的位置和元素的后代。 :is() :is() - CSS&#xff1a;层叠样式表 …

Python 进阶(三):正则表达式(re 模块)

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 1. 导入re模块2. re模块中的常用函数2.1 re.search()2.2 re.findall()2.3 re.sub()2.4…