策略模式的介绍和具体实现
❤ 作者主页:李奕赫揍小邰的博客
❀ 个人介绍:大家好,我是李奕赫!( ̄▽ ̄)~*
🍊 记得点赞、收藏、评论⭐️⭐️⭐️
📣 认真学习!!!🎉🎉
文章目录
- 策略接口
- 三种策略实现
- 全局执行器
最近做的一个AI应用答题平台,其中有应用类型分为两种,一种是测评类例如MBIT测试,一种是打分类例如科普知识得分等。并且其中评分策略也有两种,一种直接计算得出结论,一种用AI生成结论评分。因此这种生成评分结果的方法正好可以使用策略模式来实现,接下来将会用策略模式逐渐解析这个问题
策略接口
需求:针对不同的应用类别和评分策略,编写不同的实现逻辑。
策略模式是一种行为设计模式,它定义了一系列算法,并将每个算法封装到独立的类中,使得它们可以相互替换。在本项目的场景中,输入的参数是一致的(应用和用户的答案列表),并且每种实现逻辑区别较大,比较适合使用策略模式。
/**
*评分策略接口
*/
public interface ScoringStrategy {/*** 执行评分*/UserAnswer doScore(List<String> choices, App app) throws Exception;
}
三种策略实现
之前讲过,应用分两种,测评类和打分类,题目评分分为两种,自定义评分和AI评分。因此我们可以组成三种策略形式,自定义测评,自定义打分,AI测评三种类型,当然后续要是有更多类型,可以依次累加。
/**
*自定义测评策略
*/
public class CustomTestScoringStrategy implements ScoringStrategy {@Resourceprivate QuestionService questionService;@Resourceprivate ScoringResultService scoringResultService;@Overridepublic UserAnswer doScore(List<String> choices, App app) throws Exception {Long appId = app.getId();// 1. 根据 id 查询到题目和题目结果信息Question question = questionService.getOne(Wrappers.lambdaQuery(Question.class).eq(Question::getAppId, appId));List<ScoringResult> scoringResultList = scoringResultService.list(Wrappers.lambdaQuery(ScoringResult.class).eq(ScoringResult::getAppId, appId));// 2. 统计用户每个选择对应的属性个数,如 I = 10 个,E = 5 个// 初始化一个Map,用于存储每个选项的计数Map<String, Integer> optionCount = new HashMap<>();QuestionVO questionVO = QuestionVO.objToVo(question);List<QuestionContentDTO> questionContent = questionVO.getQuestionContent();// 遍历题目列表,遍历答案列表,遍历题目中的选项for (QuestionContentDTO questionContentDTO : questionContent) {for (String answer : choices) {for (QuestionContentDTO.Option option : questionContentDTO.getOptions()) {if (option.getKey().equals(answer)) {String result = option.getResult();// 如果result属性不在optionCount中,初始化为0if (!optionCount.containsKey(result)) {optionCount.put(result, 0);}// 在optionCount中增加计数optionCount.put(result, optionCount.get(result) + 1);}}}}// 3. 遍历每种评分结果,计算哪个结果的得分更高// 初始化最高分数和最高分数对应的评分结果int maxScore = 0;ScoringResult maxScoringResult = scoringResultList.get(0);// 遍历评分结果列表for (ScoringResult scoringResult : scoringResultList) {List<String> resultProp = JSONUtil.toList(scoringResult.getResultProp(), String.class);int score = resultProp.stream().mapToInt(prop -> optionCount.getOrDefault(prop, 0)).sum();if (score > maxScore) {maxScore = score;maxScoringResult = scoringResult;}}// 4. 构造返回值,填充答案对象的属性UserAnswer userAnswer = new UserAnswer();userAnswer.setAppId(appId); //赋值省略return userAnswer;}
}
/**
*自定义打分策略
*/
public class CustomScoreScoringStrategy implements ScoringStrategy {@Resourceprivate QuestionService questionService;@Resourceprivate ScoringResultService scoringResultService;@Overridepublic UserAnswer doScore(List<String> choices, App app) throws Exception {Long appId = app.getId();// 1. 根据 id 查询到题目和题目结果信息(按分数降序排序)Question question = questionService.getOne(Wrappers.lambdaQuery(Question.class).eq(Question::getAppId, appId));List<ScoringResult> scoringResultList = scoringResultService.list(Wrappers.lambdaQuery(ScoringResult.class).eq(ScoringResult::getAppId, appId).orderByDesc(ScoringResult::getResultScoreRange));// 2. 统计用户的总得分int totalScore = 0;QuestionVO questionVO = QuestionVO.objToVo(question);List<QuestionContentDTO> questionContent = questionVO.getQuestionContent();// 遍历题目列表for (int i = 0; i < questionContent.size(); i++) {QuestionContentDTO questionContentDTO = questionContent.get(i);String answer = choices.get(i);// 遍历题目中的选项for (QuestionContentDTO.Option option : questionContentDTO.getOptions()) {// 如果答案和选项的key匹配if (option.getKey().equals(answer)) {int score = Optional.of(option.getScore()).orElse(0);totalScore += score;}}}// 3. 遍历得分结果,找到第一个用户分数大于得分范围的结果,作为最终结果ScoringResult maxScoringResult = scoringResultList.get(0);for (ScoringResult scoringResult : scoringResultList) {if (totalScore >= scoringResult.getResultScoreRange()) {maxScoringResult = scoringResult;break;}}// 4. 构造返回值,填充答案对象的属性UserAnswer userAnswer = new UserAnswer();userAnswer.setAppId(appId); //赋值略return userAnswer;}
}
/**
*AI测评策略
*/
public class AiTestScoringStrategy implements ScoringStrategy {@Resourceprivate QuestionService questionService;@Resourceprivate AiManager aiManager;@Resourceprivate RedissonClient redissonClient;@Overridepublic UserAnswer doScore(List<String> choices, App app) throws Exception {Long appId = app.getId();String jsonStr = JSONUtil.toJsonStr(choices);String cacheKey = buildCacheKey(appId, jsonStr);String answerJson = answerCacheMap.getIfPresent(cacheKey);// 如果有缓存,直接返回if (StrUtil.isNotBlank(answerJson)) {// 构造返回值,填充答案对象的属性UserAnswer userAnswer = JSONUtil.toBean(answerJson, UserAnswer.class);userAnswer.setAppId(appId);userAnswer.setAppType(app.getAppType());userAnswer.setScoringStrategy(app.getScoringStrategy());userAnswer.setChoices(jsonStr);return userAnswer;}// 定义锁RLock lock = redissonClient.getLock(AI_ANSWER_LOCK + cacheKey);try {// 竞争锁boolean res = lock.tryLock(3, 15, TimeUnit.SECONDS);// 没抢到锁,强行返回if (!res) {return null;}// 抢到锁了,执行后续业务逻辑// 1. 根据 id 查询到题目Question question = questionService.getOne(Wrappers.lambdaQuery(Question.class).eq(Question::getAppId, appId));QuestionVO questionVO = QuestionVO.objToVo(question);List<QuestionContentDTO> questionContent = questionVO.getQuestionContent();// 2. 调用 AI 获取结果// 封装 PromptString userMessage = getAiTestScoringUserMessage(app, questionContent, choices);// AI 生成String result = aiManager.doSyncStableRequest(AI_TEST_SCORING_SYSTEM_MESSAGE, userMessage);// 截取需要的 JSON 信息int start = result.indexOf("{");int end = result.lastIndexOf("}");String json = result.substring(start, end + 1);// 缓存结果answerCacheMap.put(cacheKey, json);// 3. 构造返回值,填充答案对象的属性UserAnswer userAnswer = JSONUtil.toBean(json, UserAnswer.class);userAnswer.setAppId(appId);userAnswer.setAppType(app.getAppType());userAnswer.setScoringStrategy(app.getScoringStrategy());userAnswer.setChoices(jsonStr);return userAnswer;} finally {if (lock != null && lock.isLocked()) {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}}
}
以上是三种继承策略接口的doScore实现类
全局执行器
为了简化外部调用,需要根据不同的应用类别和评分策略,选择对应的策略执行,因此需要一个全局执行器。
2 种实现方式:
1)编程式,在内部计算选用何种策略:
@Service
@Deprecated
public class ScoringStrategyContext {@Resourceprivate CustomScoreScoringStrategy customScoreScoringStrategy;@Resourceprivate CustomTestScoringStrategy customTestScoringStrategy;/*** 评分*/public UserAnswer doScore(List<String> choiceList, App app) throws Exception {AppTypeEnum appTypeEnum = AppTypeEnum.getEnumByValue(app.getAppType());AppScoringStrategyEnum appScoringStrategyEnum = AppScoringStrategyEnum.getEnumByValue(app.getScoringStrategy());if (appTypeEnum == null || appScoringStrategyEnum == null) {throw new BusinessException(ErrorCode.SYSTEM_ERROR, "应用配置有误,未找到匹配的策略");}// 根据不同的应用类别和评分策略,选择对应的策略执行switch (appTypeEnum) {case SCORE:switch (appScoringStrategyEnum) {case CUSTOM:return customScoreScoringStrategy.doScore(choiceList, app);case AI:break;}break;case TEST:switch (appScoringStrategyEnum) {case CUSTOM:return customTestScoringStrategy.doScore(choiceList, app);case AI:break;}break;}throw new BusinessException(ErrorCode.SYSTEM_ERROR, "应用配置有误,未找到匹配的策略");}
}
优点是直观清晰,缺点是不利于扩展和维护。直接使用switch和case进行判断,适用于少量且类型不怎么更改的样式。
2)声明式,在每个策略类中通过接口声明对应的生效条件,适合比较规律的策略选取场景。
接口:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface ScoringStrategyConfig {/*** 应用类型* @return*/int appType();/*** 评分策略* @return*/int scoringStrategy();
}
给策略实现类补充注解,将不同的策略实现类添加注解对应用类型和评分方式进行赋值:
@ScoringStrategyConfig(appType = 0, scoringStrategy = 0)
全局执行器
/*** 评分策略执行器*/
@Service
public class ScoringStrategyExecutor {// 策略列表@Resourceprivate List<ScoringStrategy> scoringStrategyList;/*** 评分*/public UserAnswer doScore(List<String> choiceList, App app) throws Exception {Integer appType = app.getAppType();Integer appScoringStrategy = app.getScoringStrategy();if (appType == null || appScoringStrategy == null) {throw new BusinessException(ErrorCode.SYSTEM_ERROR, "应用配置有误,未找到匹配的策略");}// 根据注解获取策略for (ScoringStrategy strategy : scoringStrategyList) {if (strategy.getClass().isAnnotationPresent(ScoringStrategyConfig.class)) {ScoringStrategyConfig scoringStrategyConfig = strategy.getClass().getAnnotation(ScoringStrategyConfig.class);if (scoringStrategyConfig.appType() == appType && scoringStrategyConfig.scoringStrategy() == appScoringStrategy) {return strategy.doScore(choiceList, app);}}}throw new BusinessException(ErrorCode.SYSTEM_ERROR, "应用配置有误,未找到匹配的策略");}
}
因为用了ScoringStrategyConfig注解,所以这个实现类被加上了component注解,因此可以被spring管理扫描到。然后@Resoure注入的时候,会通过ScoringStrategy类型找到所有实现ScoringStrategy接口的实现类
之后直接调用策略全局执行器即可调用不同doScore方法。
UserAnswer userAnswerWithResult = scoringStrategyExecutor.doScore(choices, app);
这样之后就算新增加应用类型和评分方式,我们代码中执行器和调用方法也不用修改,只需要添加继承策略接口的实现类,给策略实现类补充注解即可
相关文章:
策略模式的介绍和具体实现
❤ 作者主页:李奕赫揍小邰的博客 ❀ 个人介绍:大家好,我是李奕赫!( ̄▽ ̄)~* 🍊 记得点赞、收藏、评论⭐️⭐️⭐️ 📣 认真学习!!!🎉🎉 文章目录 策略接口三种…...
MySQL InnoDB MVCC数据结构分析
1、概述 MVCC(Multiversion Concurrency Control)多版本并发控制,通过维护不同的版本号,提供一种很好的并发控制技术,这种技术能够使读写操作不冲突,提升并发性能。 MySQL InnoDB存储引擎,在更…...
MySQL 8 查看 SQL 语句的执行进度
目录 1. 查询各阶段执行进度 (1)开启收集与统计汇总执行阶段信息的功能 (2)确定执行的SQL所属的thread_id (3)查询各阶段的执行进度 2. 查询SQL语句的整体执行进度 1. 查询各阶段执行进度 ࿰…...
OpenStack 部署实践与原理解析 - Ubuntu 22.04 部署 (DevStack)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言OpenStack 原理详解1. OpenStack 的架构2. OpenStack 的工作原理3. OpenStack 的 API4. 扩展性和模块化 OpenStack 安装方式比较1. DevStack2. Kolla3. OpenSta…...
【软件工程】可行性研究
一、目的 二、任务 三、步骤 四、结果:可行性研究报告 例题 选择题...
乌克兰因安全风险首次禁用Telegram
据BleepingComputer消息,乌克兰国家网络安全协调中心 (NCCC) 以国家安全为由,已下令限制在政府机构、军事单位和关键基础设施内使用 Telegram 消息应用程序。 这一消息通过NCCC的官方 Facebook 账号对外发布,在公告中乌…...
[SDX35]SDX35如何查看GPIO的Base值
SDX35 SDX35介绍 SDX35设备是一种多模调制解调器芯片,支持 4G/5G sub-6 技术。它是一个4nm芯片专为实现卓越的性能和能效而设计。它包括一个 1.9 GHz Cortex-A7 应用处理器。 SDX35主要特性 ■ 3GPP Rel. 17 with 5G Reduced Capability (RedCap) support. Backward compati…...
【Linux学习】【Ubuntu入门】2-1-1 vim编辑器设置
设置TAB键为4字节及显示行号 VIM编辑器默认TAB键为8空格,改为4空格 输入命令sudo vi /etc/vim/vimrc回车后输入密码按键盘下键到最后,按下“a”进入编辑模式,输入set ts4设置为4空格下一行输入set nu显示行号...
全栈开发(一):springBoot3+mysql初始化
1.开发环境准备 1.开发工具 2.jdk下载 官网下载java17 3.java环境变量配置 用户变量: ①.JAVA_HOME ②.path 4.mysql下载 b站随便搜 5.新建项目 6.maven配置 可以下载zip放到目录里 这里是配置好的 repository文件夹:为maven提供下载的文件存放…...
有关若依登录过程前端的对应处理学习
导言 在用C#搞完个后端后想用若依的前端做对接,不过很久没搞过若依了,想趁这个二次开发的过程记录熟悉一下登录的过程 过程 验证,在permission.js的路由守卫,这里在用户发起api请求时会验证用户的请求是否有token,对…...
django使用笔记6--docker部署
django使用笔记--docker部署 多环境配置创建环境变量配置文件静态资源配置dockerfile配置 由于服务器中python版本和依赖与本地开发环境不同,且centOS7中python及依赖安装更新较为麻烦,所以采用docker容器部署 多环境配置 多环境配置类似Spring中的多环…...
高性能、高可靠,MK SD卡让数据存储无忧!
文章目录 SD卡(Secure Digital Memory Card),作为当代数字生活中不可或缺的存储媒介,凭借其卓越的数据传输效率、灵活的热插拔功能以及惊人的存储容量,在多个领域大放异彩。从日常使用的智能手机、平板电脑到追求极致体…...
NetAssist测试TCP和UDP
由于在Windows下经常使用NetAssist.exe这款网络调试工具进行TCP、UDP的服务端、客户端的监听,对于需要编写各种通信协议的TCP服务端、客户端以及UDP通信程序来说是很方便的。下载地址:http://free.cmsoft.cn/download/cmsoft/assistant/netassist5.0.14.…...
mcuboot使用介绍
准备工作 硬件平台选择 确保你的微控制器单元(MCU)是 MCUboot 所支持的类型。查看 MCUboot 的文档或官方支持列表,了解其兼容的 MCU 系列和硬件平台。根据硬件平台的设计,将微控制器与相关的外设(如闪存、通信接口等&a…...
如何在 Linux 终端使用 GET 和 POST 请求
文章目录 1、GET请求基本请求带有请求头带有参数将响应保存成文件 2、POST请求基本请求发送JSON格式的POST请求体使用文件作为POST请求体使用时注意 1、GET请求 基本请求 在Linux中,发送GET请求通常使用 curl 命令,curl 的默认行为就是发送GET请求&…...
主从数据库同步配置详解(MySQL/MariaDB)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、环境准备与安装配置本地部署MySQLUbuntu 系统:CentOS 系统: MariaDBUbuntu 系统:CentOS 系统: 容器部署MySQ…...
台式机通过笔记本上网
概述: ①将wifi共享给网口 ②网口配置成自协商IP和DNS即可 一、背景 由于台式机只有网口,没得wifi网卡,因此想通过笔记本连wifi,再通过网线将笔记本和台式机连接起来,从而实现台式机通过笔记本的wifi上网,即让笔记本当台式机的…...
golang雪花算法实现64位的ID
推荐学习文档 golang应用级os框架,欢迎stargolang应用级os框架使用案例,欢迎star案例:基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总想学习更多golang知识,这里有免费的golang学习笔…...
LeetCode 137. 只出现一次的数字 II
LeetCode 137. 只出现一次的数字 II 给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。 示例 1:…...
新书推荐——《深度学习精粹与PyTorch实践》
深度学习绝非不可窥探的黑箱!深入理解其模型和算法的实际运作机制,是驾驭并优化结果的关键。你无需成为数学专家或资深数据科学家,同样能够掌握深度学习系统内部的工作原理。 本书旨在通过深入浅出的方式,为你揭示这些原理,让你在理解和解释…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...
