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

策略模式的介绍和具体实现

❤ 作者主页:李奕赫揍小邰的博客
❀ 个人介绍:大家好,我是李奕赫!( ̄▽ ̄)~*
🍊 记得点赞、收藏、评论⭐️⭐️⭐️
📣 认真学习!!!🎉🎉

文章目录

  • 策略接口
  • 三种策略实现
  • 全局执行器

 最近做的一个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);

这样之后就算新增加应用类型和评分方式,我们代码中执行器和调用方法也不用修改,只需要添加继承策略接口的实现类,给策略实现类补充注解即可

http://www.lryc.cn/news/447437.html

相关文章:

  • MySQL InnoDB MVCC数据结构分析
  • MySQL 8 查看 SQL 语句的执行进度
  • OpenStack 部署实践与原理解析 - Ubuntu 22.04 部署 (DevStack)
  • 【软件工程】可行性研究
  • 乌克兰因安全风险首次禁用Telegram
  • [SDX35]SDX35如何查看GPIO的Base值
  • 【Linux学习】【Ubuntu入门】2-1-1 vim编辑器设置
  • 全栈开发(一):springBoot3+mysql初始化
  • 有关若依登录过程前端的对应处理学习
  • django使用笔记6--docker部署
  • 高性能、高可靠,MK SD卡让数据存储无忧!
  • NetAssist测试TCP和UDP
  • mcuboot使用介绍
  • 如何在 Linux 终端使用 GET 和 POST 请求
  • 主从数据库同步配置详解(MySQL/MariaDB)
  • 台式机通过笔记本上网
  • golang雪花算法实现64位的ID
  • LeetCode 137. 只出现一次的数字 II
  • 新书推荐——《深度学习精粹与PyTorch实践》
  • Docker:解决开发运维问题的开源容器化平台
  • 多线程计算π
  • JAVA开源项目 足球俱乐部管理后台 计算机毕业设计
  • Arthas memory(查看 JVM 内存信息)
  • C#车辆登记证识别API接口集成示例-车辆合格证识别免费的API接口
  • 学习笔记每日一题
  • 【巅峰算力,静谧之作】4卡4090GPU深度学习“静音”服务器
  • 论JAVA 两种“对象比较接口“的区别
  • 在线代理提取IP:一文详解其含义和应用
  • linux-字符串函数使用
  • Python 06 Set