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

基于注解优雅的实现接口幂等性

一、什么是幂等性

简单来说,就是对一个接口执行重复的多次请求,与一次请求所产生的结果是相同的,听起来非常容易理解,但要真正的在系统中要始终保持这个目标,是需要很严谨的设计的,在实际的生产环境下,我们应该保证任何接口都是幂等的,而如何正确的实现幂等,就是本文要讨论的内容。

二、哪些请求天生就是幂等的

首先,我们要知道查询类的请求一般都是天然幂等的,除此之外,删除请求在大多数情况下也是幂等的,但是ABA场景下除外。

举一个简单的例子

比如,先请求了一次删除A的操作,但由于响应超时,又自动请求了一次删除A的操作,如果在两次请求之间,又插入了一次A,而实际上新插入的这一次A,是不应该被删除的,这就是ABA问题,不过,在大多数业务场景中,ABA问题都是可以忽略的。

除了查询和删除之外,还有更新操作,同样的更新操作在大多数场景下也是天然幂等的,其例外是也会存在ABA的问题,更重要的是,比如执行update table set a = a + 1 where v = 1这样的更新就非幂等了。

最后,就还剩插入了,插入大多数情况下都是非幂等的,除非是利用数据库唯一索引来保证数据不会重复产生。

三、为什么需要幂等

1.超时重试

当发起一次RPC请求时,难免会因为网络不稳定而导致请求失败,一般遇到这样的问题我们希望能够重新请求一次,正常情况下没有问题,但有时请求实际上已经发出去了,只是在请求响应时网络异常或者超时,此时,请求方如果再重新发起一次请求,那被请求方就需要保证幂等了。

2.异步回调

异步回调是提升系统接口吞吐量的一种常用方式,很明显,此类接口一定是需要保证幂等性的。

3.消息队列

现在常用的消息队列框架,比如:Kafka、RocketMQ、RabbitMQ在消息传递时都会采取At least once原则(也就是至少一次原则,在消息传递时,不允许丢消息,但是允许有重复的消息),既然消息队列不保证不会出现重复的消息,那消费者自然要保证处理逻辑的幂等性了。

四、实现幂等的关键因素

关键因素1

幂等唯一标识,可以叫它幂等号或者幂等令牌或者全局ID,总之就是客户端与服务端一次请求时的唯一标识,一般情况下由客户端来生成,也可以让第三方来统一分配。

关键因素2

有了唯一标识以后,服务端只需要确保这个唯一标识只被使用一次即可,一种常见的方式就是利用数据库的唯一索引。

五、注解实现幂等性

  1.  定义DistributedLock注解

    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DistributedLock {/*** 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key* 支持使用spEl表达式*/String key();/*** 保证业务接口的key的唯一性,否则失去了分布式锁的意义 锁key 前缀*/String keyPrefix() default "";/*** 是否在等待时间内获取锁,如果在等待时间内无法获取到锁,则返回失败*/boolean tryLok() default false;/*** 获取锁的最大尝试时间 ,会尝试tryTime时间获取锁,在该时间内获取成功则返回,否则抛出获取锁超时异常,tryLok=true时,该值必须大于0。**/long tryTime() default 0;/*** 加锁的时间,超过这个时间后锁便自动解锁*/long lockTime() default 30;/*** tryTime 和 lockTime的时间单位*/TimeUnit unit() default TimeUnit.SECONDS;/*** 是否公平锁,false:非公平锁,true:公平锁*/boolean fair() default false;
    }
  2. 定义DistributedLockAspect Lock切面

    @Aspect
    @Slf4j
    public class DistributedLockAspect {@Resourceprivate IDistributedLock distributedLock;/*** SpEL表达式解析*/private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();/*** 用于获取方法参数名字*/private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();@Pointcut("@annotation(com.yt.bi.common.redis.distributedlok.annotation.DistributedLock)")public void distributorLock() {}@Around("distributorLock()")public Object around(ProceedingJoinPoint pjp) throws Throwable {// 获取DistributedLockDistributedLock distributedLock = this.getDistributedLock(pjp);// 获取 lockKeyString lockKey = this.getLockKey(pjp, distributedLock);ILock lockObj = null;try {// 加锁,tryLok = true,并且tryTime > 0时,尝试获取锁,获取不到超时异常if (distributedLock.tryLok()) {if(distributedLock.tryTime() <= 0){throw new IdempotencyException("tryTime must be greater than 0");}lockObj = this.distributedLock.tryLock(lockKey, distributedLock.tryTime(), distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());} else {lockObj = this.distributedLock.lock(lockKey, distributedLock.lockTime(), distributedLock.unit(), distributedLock.fair());}if (Objects.isNull(lockObj)) {throw new IdempotencyException("Duplicate request for method still in process");}return pjp.proceed();} catch (Exception e) {throw e;} finally {// 解锁this.unLock(lockObj);}}/*** @param pjp* @return* @throws NoSuchMethodException*/private DistributedLock getDistributedLock(ProceedingJoinPoint pjp) throws NoSuchMethodException {String methodName = pjp.getSignature().getName();Class clazz = pjp.getTarget().getClass();Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes();Method lockMethod = clazz.getMethod(methodName, par);DistributedLock distributedLock = lockMethod.getAnnotation(DistributedLock.class);return distributedLock;}/*** 解锁** @param lockObj*/private void unLock(ILock lockObj) {if (Objects.isNull(lockObj)) {return;}try {this.distributedLock.unLock(lockObj);} catch (Exception e) {log.error("分布式锁解锁异常", e);}}/*** 获取 lockKey** @param pjp* @param distributedLock* @return*/private String getLockKey(ProceedingJoinPoint pjp, DistributedLock distributedLock) {String lockKey = distributedLock.key();String keyPrefix = distributedLock.keyPrefix();if (StringUtils.isBlank(lockKey)) {throw new IdempotencyException("Lok key cannot be empty");}if (lockKey.contains("#")) {this.checkSpEL(lockKey);MethodSignature methodSignature = (MethodSignature) pjp.getSignature();// 获取方法参数值Object[] args = pjp.getArgs();lockKey = getValBySpEL(lockKey, methodSignature, args);}lockKey = StringUtils.isBlank(keyPrefix) ? lockKey : keyPrefix + lockKey;return lockKey;}/*** 解析spEL表达式** @param spEL* @param methodSignature* @param args* @return*/private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {// 获取方法形参名数组String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());if (paramNames == null || paramNames.length < 1) {throw new IdempotencyException("Lok key cannot be empty");}Expression expression = spelExpressionParser.parseExpression(spEL);// spring的表达式上下文对象EvaluationContext context = new StandardEvaluationContext();// 给上下文赋值for (int i = 0; i < args.length; i++) {context.setVariable(paramNames[i], args[i]);}return expression.getValue(context).toString();}/*** SpEL 表达式校验** @param spEL* @return*/private void checkSpEL(String spEL) {try {ExpressionParser parser = new SpelExpressionParser();parser.parseExpression(spEL, new TemplateParserContext());} catch (Exception e) {log.error("spEL表达式解析异常", e);throw new IdempotencyException("Invalid SpEL expression [" + spEL + "]");}}
    }
  3. 定义分布式锁注解版启动元注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import({DistributedLockAspect.class})
    public @interface EnableDistributedLock {
    }
  4. 定义IDistributedLock分布式锁接口

    public interface IDistributedLock {/*** 获取锁,默认30秒失效,失败一直等待直到获取锁** @param key 锁的key* @return 锁对象*/ILock lock(String key);/*** 获取锁,失败一直等待直到获取锁** @param key      锁的key* @param lockTime 加锁的时间,超过这个时间后锁便自动解锁; 如果lockTime为-1,则保持锁定直到显式解锁* @param unit     {@code lockTime} 参数的时间单位* @param fair     是否公平锁* @return 锁对象*/ILock lock(String key, long lockTime, TimeUnit unit, boolean fair);/*** 尝试获取锁,30秒获取不到超时异常,锁默认30秒失效** @param key     锁的key* @param tryTime 获取锁的最大尝试时间* @return* @throws Exception*/ILock tryLock(String key, long tryTime) throws Exception;/*** 尝试获取锁,获取不到超时异常** @param key      锁的key* @param tryTime  获取锁的最大尝试时间* @param lockTime 加锁的时间* @param unit     {@code tryTime @code lockTime} 参数的时间单位* @param fair     是否公平锁* @return* @throws Exception*/ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair) throws Exception;/*** 解锁** @param lock* @throws Exception*/void unLock(Object lock);/*** 释放锁** @param lock* @throws Exception*/default void unLock(ILock lock) {if (lock != null) {unLock(lock.getLock());}}}
  5. IDistributedLock实现类

    @Slf4j
    @Component
    public class RedissonDistributedLock implements IDistributedLock {@Resourceprivate RedissonClient redissonClient;/*** 统一前缀*/@Value("${redisson.lock.prefix:bi:distributed:lock}")private String prefix;@Overridepublic ILock lock(String key) {return this.lock(key, 0L, TimeUnit.SECONDS, false);}@Overridepublic ILock lock(String key, long lockTime, TimeUnit unit, boolean fair) {RLock lock = getLock(key, fair);// 获取锁,失败一直等待,直到获取锁,不支持自动续期if (lockTime > 0L) {lock.lock(lockTime, unit);} else {// 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30slock.lock();}return new ILock(lock, this);}@Overridepublic ILock tryLock(String key, long tryTime) throws Exception {return this.tryLock(key, tryTime, 0L, TimeUnit.SECONDS, false);}@Overridepublic ILock tryLock(String key, long tryTime, long lockTime, TimeUnit unit, boolean fair)throws Exception {RLock lock = getLock(key, fair);boolean lockAcquired;// 尝试获取锁,获取不到超时异常,不支持自动续期if (lockTime > 0L) {lockAcquired = lock.tryLock(tryTime, lockTime, unit);} else {// 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30slockAcquired = lock.tryLock(tryTime, unit);}if (lockAcquired) {return new ILock(lock, this);}return null;}/*** 获取锁** @param key* @param fair* @return*/private RLock getLock(String key, boolean fair) {RLock lock;String lockKey = prefix + ":" + key;if (fair) {// 获取公平锁lock = redissonClient.getFairLock(lockKey);} else {// 获取普通锁lock = redissonClient.getLock(lockKey);}return lock;}@Overridepublic void unLock(Object lock) {if (!(lock instanceof RLock)) {throw new IllegalArgumentException("Invalid lock object");}RLock rLock = (RLock) lock;if (rLock.isLocked()) {try {rLock.unlock();} catch (IllegalMonitorStateException e) {log.error("释放分布式锁异常", e);}}}
    }
  6. 定义ILock锁对象

    import lombok.AllArgsConstructor;
    import lombok.Getter;import java.util.Objects;/*** <p>* RedissonLock 包装的锁对象 实现AutoCloseable接口,在java7的try(with resource)语法,不用显示调用close方法* </p>* @since 2023-06-08 16:57*/
    @AllArgsConstructor
    public class ILock implements AutoCloseable {/*** 持有的锁对象*/@Getterprivate Object lock;/*** 分布式锁接口*/@Getterprivate IDistributedLock distributedLock;@Overridepublic void close() throws Exception {if(Objects.nonNull(lock)){distributedLock.unLock(lock);}}
    }

六、使用示例 

 启动类添加@EnableDistributedLock启用注解支持

@SpringBootApplication
@EnableDistributedLock
public class BiCenterGoodsApplication {public static void main(String[] args) {SpringApplication.run(BiCenterGoodsApplication.class, args);}
}

@DistributedLock标注需要使用分布式锁的方法 

    @ApiOperation("编辑SKU供应商供货信息")@PostMapping("/editSupplierInfo")//@DistributedLock(key = "#dto.sku + '-' + #dto.skuId", lockTime = 10L, keyPrefix = "sku-")@DistributedLock(key = "#dto.sku", lockTime = 10L, keyPrefix = "sku-")public R<Boolean> editSupplierInfo(@RequestBody @Validated ProductSkuSupplierInfoDTO dto) {return R.ok(productSkuSupplierMeasureService.editSupplierInfo(dto));}
#dto.sku是 SpEL表达式。Spring中支持的它都支持的。比如调用静态方法,三目表达式。SpEL 可以使用方法中的任何参数。SpEL表达式参考

从原理到实践,分析 Redis 分布式锁的多种实现方案(一)

从原理到实践,分析 Redisson 分布式锁的实现方案(二)

Spring Boot 集成 Redisson分布式锁


参考文章:一个注解,优雅的实现接口幂等性! 

相关文章:

基于注解优雅的实现接口幂等性

一、什么是幂等性 简单来说&#xff0c;就是对一个接口执行重复的多次请求&#xff0c;与一次请求所产生的结果是相同的&#xff0c;听起来非常容易理解&#xff0c;但要真正的在系统中要始终保持这个目标&#xff0c;是需要很严谨的设计的&#xff0c;在实际的生产环境下&…...

flutter:webview_flutter和flutter_inappwebview的简单使用

前言 最近在研究如何在应用程序中嵌入Web视图&#xff0c;发现有两个库不错。 一个是官方维护、一个是第三方维护。因为没说特别的需求&#xff0c;就使用了官方库&#xff0c;实现一些简单功能是完全ok的 webview_flutter 不建议使用&#xff0c;因为效果不怎么样&#xf…...

opencv进阶09-视频处理cv2.VideoCapture示例(打开本机电脑摄像头)

视频信号&#xff08;以下简称为视频&#xff09;是非常重要的视觉信息来源&#xff0c;它是视觉处理过程中经常要处理的一类信号。实际上&#xff0c;视频是由一系列图像构成的&#xff0c;这一系列图像被称为帧&#xff0c;帧是以固定的时间间隔从视频中获取的。获取&#xf…...

大语言模型与语义搜索;钉钉个人版启动内测,提供多项AI服务

&#x1f989; AI新闻 &#x1f680; 钉钉个人版启动内测&#xff0c;提供多项AI服务 摘要&#xff1a;钉钉个人版正式开始内测&#xff0c;面向小团队、个人用户、高校大学生等人群。该版本具有AI为核心的功能&#xff0c;包括文生文AI、文生图AI和角色化对话等。用户可通过…...

小程序-基于vant的Picker组件实现省市区选择

一、原因 因vant/area-data部分的市/区数据跟后台使用的高德/腾讯省市区有所出入&#xff0c;故须保持跟后台用同一份数据&#xff0c;所以考虑以下几个组件 1、Area 2、Cascader 3、Picker 因为使用的是高德地图的省市区json文件&#xff0c;用area的话修改结构代价太大&…...

智慧水利利用4G物联网技术实现远程监测、控制、管理

智慧水利工业路由器是集合数据采集、实时监控、远程管理的4G物联网通讯设备&#xff0c;能够让传统水利系统实现智能化的实时监控和远程管理。工业路由器利用4G无线网络技术&#xff0c;能够实时传输数据和终端信息&#xff0c;为水利系统的运维提供有效的支持。 智慧水利系统是…...

sql server Varchar转换为Datetime

将Varchar转换为Datetime是一个常见的需求&#xff0c;在处理日期和时间数据时特别有用。在SQL Server中&#xff0c;可以使用CONVERT函数或CAST函数将Varchar转换为Datetime。 使用CONVERT函数 CONVERT函数可以将一个值从一个类型转换为另一个类型。以下是使用CONVERT函数将…...

什么文件传输协议才能保障跨国文件传输安全又稳定

在当今的全球化时代&#xff0c;跨国文件传输是一种常见而又重要的需求&#xff0c;无论是个人还是企业&#xff0c;都需要通过网络来分享和交换各种类型和大小的文件。但是&#xff0c;跨国文件传输也面临着许多挑战和风险&#xff0c;如何选择一个合适的文件传输协议&#xf…...

LeetCode笔记:Weekly Contest 359

LeetCode笔记&#xff1a;Weekly Contest 359 1. 题目一 1. 解题思路2. 代码实现 2. 题目二 1. 解题思路2. 代码实现 3. 题目三 1. 解题思路2. 代码实现 4. 题目四 1. 解题思路2. 代码实现 比赛链接&#xff1a;https://leetcode.com/contest/weekly-contest-359 1. 题目一 …...

使用Java和ChatGPT Api来创建自己的大模型聊天机器人

文章目录 前言ChatGPT Api简析Chatfunction call Embeddings 制作机器人上下文向量数据库 更多场景介绍扩展阅读 前言 什么是大模型&#xff1f; 大型语言模型&#xff08;LLM&#xff09;是一种深度学习模型&#xff0c;它使用大量数据进行预训练&#xff0c;并能够通过提示工…...

Maven介绍_下载_安装_使用_原理

文章目录 1 Maven介绍1.1 Maven是介绍1.2 Maven的作用 2 Maven下载与安装2.1 官网下载2.2 文件目录2.3 环境配置 3 Maven基础概念3.1 仓库分类3.2 依赖坐标3.3 坐标组成 4 Maven配置4.1 本地仓库配置4.2 远程仓库的设置4.3 镜像仓库配置4.4 IDEA配置Maven 5 Maven项目创建5.1 M…...

算法通关村十一关 | 位运算的规则

1.数字在计算机中的表示 机器数&#xff1a;一个数在计算机中的二进制表示形式&#xff0c;叫做这个数的机器数。机器数是自带符号的&#xff0c;在计算机用一个数的最高位存放符号&#xff0c;整数为0&#xff0c;负数为1。比如&#xff0c;十进制中的数3&#xff0c;计算机字…...

【Rust】Rust学习 第十五章智能指针

指针 &#xff08;pointer&#xff09;是一个包含内存地址的变量的通用概念。这个地址引用&#xff0c;或 “指向”&#xff08;points at&#xff09;一些其他数据。Rust 中最常见的指针是第四章介绍的 引用&#xff08;reference&#xff09;。引用以 & 符号为标志并借用…...

炒股怎样加杠杆?关于股票杠杠平台比例的选择知识分析

在股票市场中&#xff0c;加杠杆是一种常见的投资策略&#xff0c;可以帮助投资者提升收益&#xff0c;但也伴随着更高的风险。本文将介绍炒股加杠杆的具体步骤和股票杠杆平台比例选择的知识分析&#xff0c;帮助读者更好地了解并使用这一策略。 一、炒股加杠杆的步骤 1. 选择…...

【jenkins】jenkins流水线构建打包jar,生成docker镜像,重启docker服务的过程,在jenkins上一键完成,实现提交代码自动构建的功能

【jenkins】jenkins流水线构建打包jar&#xff0c;生成docker镜像&#xff0c;重启docker服务的过程&#xff0c;在jenkins上一键完成&#xff0c;实现提交代码自动构建&#xff0c;服务重启&#xff0c;服务发布的功能。一键实现。非常的舒服。 1. 启动脚本 shell脚本 这是 s…...

Pytest使用fixture实现token共享

同学们在做pytest接口自动化时&#xff0c;会遇到一个场景就是不同的测试用例需要有一个登录的前置步骤&#xff0c;登录完成后会获取到token&#xff0c;用于之后的代码中。首先我先演示一个常规的做法。 首先在conftest定义一个login的方法&#xff0c;方法返回token pytes…...

You have docker-compose v1 installed, but we require Docker Compose v2.

curl -SL https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose chmod x /usr/local/bin/docker-compose docker-compose --version...

nlopt在windows上的安装使用

nlopt在windows上的安装使用 目录 nlopt在windows上的安装使用一、nlopt下载二、def转lib三、代码 一、nlopt下载 1.下载nlopt库&#xff1a;https://nlopt.readthedocs.io/en/latest/ 2.解压 3.下载dll和def&#xff1a;http://ab-initio.mit.edu/wiki/index.php?titleNLopt…...

【React学习】React中的setState方法

1. setState概述 setState 是React框架中&#xff0c;用于更新组件状态的方法。 setState 方法由React组件继承自 React.Component 类的一部分。通过调用 setState&#xff0c;可以告诉 React要更新组件的状态&#xff0c;并触发组件的重新渲染。 this.setState(newState, ca…...

ATTCK实战系列——红队实战(一)

目录 搭建环境问题 靶场环境 web 渗透 登录 phpmyadmin 应用 探测版本 写日志获得 webshell 写入哥斯拉 webshell 上线到 msf 内网信息收集 主机发现 流量转发 端口扫描 开启 socks 代理 服务探测 getshell 内网主机 浏览器配置 socks 代理 21 ftp 6002/700…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…...

el-switch文字内置

el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !

我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...

上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式

简介 在我的 QT/C 开发工作中&#xff0c;合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式&#xff1a;工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...