工厂模式,策略模式,代理模式,单例模式在项目中的应用

项目背景:

首先这篇文章是总结了OJ项目和AI答题平台项目(和一点点的聚合搜索项目)中设计模式的文章

在项目中也用了很多次的设计模式,我感觉起来,这些设计模式的作用就是提高项目的扩展性和降低耦合性

工厂模式:

设计模式(工厂模式,模板方法模式,单例模式)_工厂方法模式与模板方法模式-CSDN博客

关于工厂模式的介绍,贴一篇文章,里面有跑一些demo

OJ项目工厂模式

直接开始对OJ项目开始记录:

首先我们这里有一个沙箱的接口,并且三个实现类

分别对应

实例代码沙箱(纯属给自己测试用的)
远程调用代码沙箱(项目的核心流程)
第三方代码沙箱(可接入第三方的代码沙箱)

这三个沙箱都需要去实现这个沙箱接口。

接着我们循序渐进,我们看第一版的调用沙箱的代码:

这里先粘贴一张自己的工厂模式的文章中的一张图片

对应到OJ项目中

/*** 第一版代码* 最朴实无华的自己去创建对应沙箱的实体类*/public static void firstCode(String[] args) {CodeSandBox codeSandBox = new RemoteCodeSandBox();ExecuteCodeRequest executeCodeRequest = ExecuteCodeRequest.builder().code("System.out.println(\"hello world\");").language("java").inputList(new ArrayList<>()).build();ExecuteResponse executeResponse = codeSandBox.executeCode(executeCodeRequest);System.out.println(executeResponse);}

 看到代码中我们自己想要什么沙箱,就自己去new

这样肯定很麻烦嘛,所以我们进行优化:

根据这个demo所说,我们只需要关系我们需要什么类型,往里传一个type即可

这个工厂就会自动帮我们把这个对象new出来,给我们,我们只需要用每个产品都是实现的接口来接收。

@Value("${codeSandBox.type:example}")
private String type;
/***  第二版代码* 使用工厂模式,根据语言创建对应的沙箱实体类* 这里其实还用了一个注册器模式(单例模式)在Bean对象创建之后将对应的实体类加入到map中,直接调用* 第三版代码* 参数配置化,将这个type配置到yml文件中,使用者直接修改即可* @Value 注解不能用于 static 字段*/public void ThirdCode() {Map<String, CodeSandBox> codeSandboxMap = new HashMap<>();codeSandboxMap.put("example",new ExampleCodeSandBox());codeSandboxMap.put("remote",new RemoteCodeSandBox());codeSandboxMap.put("thirdParty",new ThirdPartyCodeSandBox());System.out.println(type);CodeSandBox codeSandBox = codeSandboxMap.get(type);ExecuteCodeRequest executeCodeRequest = ExecuteCodeRequest.builder().code("System.out.println(\"hello world\");").language("java").inputList(new ArrayList<>()).build();ExecuteResponse executeResponse = codeSandBox.executeCode(executeCodeRequest);System.out.println(executeResponse);}

这里有注意点:

将这个type可以配置到yml文件中,这也是让别人更好调用我们这个开源项目的小技巧

代码中还缓存了一个map,这一段可以放到一个类中,在Bean对象都加载之后再缓存

这个在聚合搜索文章有讲聚合平台项目优化(门面模式,适配器模式,注册器模式)_聚合平台适配器-CSDN博客 

通过上面的操作,我们就可以不用自己去new对应的沙箱对象了

只需要关心我们的type就行。

代理模式:

这里OJ项目还用到了一个代理模式对调用代码沙箱这个逻辑进行增强

在调用之前和调用之后都打上了日志

这里的这个代理模式很好理解:就是和AOP一起理解即可

具体代码实现:

代理类:
/*** 代码沙箱代理* 这里的代理听起来很复杂,实现起来就是写一个类.* 调用代码沙箱的用户不需要自己去手动打日志,只需要去调用这个代理类即可* 代理类会自动打日志* 说到代理类,就经典的案例就是房屋中介,房屋中介就是代理类,租房子的人不需要自己去找房子,只需要去找中介即可*/
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
public class CodeSandBoxProxy implements CodeSandBox {private CodeSandBox codeSandBox;@Overridepublic ExecuteResponse executeCode(ExecuteCodeRequest executeCodeRequest) {log.info("代码沙箱执行前");ExecuteResponse executeResponse = codeSandBox.executeCode(executeCodeRequest);log.info("代码沙箱执行后");return executeResponse;}
}
工厂类:
@Component
public class CodeSandFactory {private Map<String, CodeSandBox> codeSandboxMap = new HashMap<>();@PostConstructprivate void init() {codeSandboxMap.put("example",new ExampleCodeSandBox());codeSandboxMap.put("remote",new RemoteCodeSandBox());codeSandboxMap.put("thirdParty",new ThirdPartyCodeSandBox());}public CodeSandBox getCodeSandBox(String type) {return codeSandboxMap.get(type);}
}

 这里就把每个沙箱缓存到一个map中

 具体调用代码:
/***  第四版代码*  利用代理模式对调用沙箱功能进行优化*  调用前和调用后自动打上日志*/@Testvoid ForthCode() {CodeSandBox codeSandBox = codeSandFactory.getCodeSandBox(type);System.out.println(type);CodeSandBoxProxy codeSandBoxProxy = new CodeSandBoxProxy(codeSandBox);ExecuteCodeRequest executeCodeRequest = ExecuteCodeRequest.builder().code("System.out.println(\"hello world\");").language("java").inputList(new ArrayList<>()).build();ExecuteResponse executeResponse = codeSandBoxProxy.executeCode(executeCodeRequest);System.out.println(executeResponse);}

AI答题平台项目工厂模式

这里的代码逻辑就会更复杂一点。可以将这里的一个一个策略类看成一个个对象,你只需要传app进来,我就读取你的app中的两个字段,就可以判断出你给你这个app判题的是那种评分策略(AI评分,自定义评分,得分评分)

这里代码复杂得原因就是涉及到了Java中的注解和反射的使用

首先还是定义一个策略接口:

public interface ScoringStrategy {public UserAnswer doScoring(List<String> choices, App app);
}

后面的三个策略都需要去实现这个接口 

定义注解类:

这段代码定义了一个名为 ScoringStrategyConfig 的 Java 注解(Annotation)。注解是一种元数据,用于为代码元素(如类、方法、变量等)提供额外的信息。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface ScoringStrategyConfig {/*** 应用类型* @return*/int appType();/*** 评分策略* @return*/int scoringStrategy();
}

这个注解上还有两个注解:

 @Target(ElementType.TYPE):指定这个注解的作用域:类,接口和枚举
@Retention(RetentionPolicy.RUNTIME) 指定了注解在运行时可用,

在自己的每个策略上加入注解,并指定应用类型和评分策略 

 工厂类实现:
@Service
public class ScoringStrategyExecutor {@Resourceprivate List<ScoringStrategy> strategies;public UserAnswer doScore(List<String> choices, App app) {final Integer appType = app.getAppType();final AppTypeEnum enumByValue = AppTypeEnum.getEnumByValue(appType);ThrowUtils.throwIf((enumByValue == null||appType==null), ErrorCode.PARAMS_ERROR,"appType参数错误");for (ScoringStrategy strategy : strategies) {if(strategy.getClass().isAnnotationPresent(ScoringStrategyConfig.class)){ScoringStrategyConfig annotation = strategy.getClass().getAnnotation(ScoringStrategyConfig.class);if(annotation.appType()==app.getAppType()&&annotation.scoringStrategy()==app.getScoringStrategy())return strategy.doScoring(choices,app);}}throw new BusinessException(ErrorCode.SYSTEM_ERROR, "应用配置有误,未找到匹配的策略");}
}

代码逻辑分析:

    @Resourceprivate List<ScoringStrategy> strategies;

在你的代码中,你使用了 @Resource 注解来注入 List<ScoringStrategy>。这个注解会自动扫描并注入所有实现了 ScoringStrategy 接口的 Bean。这意味着,只要你的 Spring 容器中存在实现了 ScoringStrategy 接口的 Bean,它们就会被自动注入到 strategies 列表中。

接着就是对传进来的app的内容进行判断

随后遍历这个 strategies 列表

if(strategy.getClass().isAnnotationPresent(ScoringStrategyConfig.class)){

这行代码检查当前策略类是否包含ScoringStrategyConfig注解。isAnnotationPresent方法用于判断某个类是否被指定注解修饰

判断你这个类是有这个注解的:

获取注解并检查条件:

ScoringStrategyConfig annotation = strategy.getClass().getAnnotation(ScoringStrategyConfig.class);
if(annotation.appType()==app.getAppType()&&annotation.scoringStrategy()==app.getScoringStrategy())

如果策略类包含ScoringStrategyConfig注解,代码会获取该注解的实例,并检查注解中的appTypescoringStrategy属性是否与当前应用app的类型和评分策略匹配。

最后就是执行这个具体的评分策略

return strategy.doScoring(choices, app);

小思考小总结

工厂模式就是简化你创建对象的这个过程

你不想自己去new对象,你就搞个“工厂”,让这个工厂给你new对象

你只需要传值,你要说清楚你想要什么样的对象

那工厂怎么根据你传的值来new对应的对象呢?

最简单的办法就是在工厂类中写switch或者if-else

OJ项目就是用了一个注册器的设计模式,将所有沙箱对象缓存到map中

AI答题平台呢就是用了注解+放射的方式,在策略对象上+注解,最后遍历列表返回对应的策略对象

工厂模式和这个门面模式之间的一些思考

我在整理笔记的时候,感觉都是从前端传一个type,或者什么值巴拉巴拉的

然后简化过程

后面问了GPT,我的理解其实差不会特别大

工厂模式更注重对象的创建,你传我一个值,我还你一个对象

但是门面模式呢,更注重接口或者系统的调用,在聚合搜索中,我有三个接口,分别调用文章,用户,图片。

我前端传个参数,我后端就对应去调那个接口。

策略模式:

策略模式就是用来解决很多if else分支的问题

但是策略模式通常是要搭配一些其它东西才能比较好的使用

OJ项目策略模式应用:

为什么在OJ项目中要用策略模式:

我们知道,每个语言做相同的题目的耗时和空间都不一样,如果不指定的话,那对Java非常不公平

首先也是不变先来个策略接口:每种策略都需要实现这个接口

/*** 判题策略接口*/
public interface JudgeStrategy {JudgeInfo doJudge(JudgeContext judgeContext);}

下面只写了两个策略:默认编程语言策略和Java编程语言策略:

这里还有一个JudgeContext上下文这个类,用来(用于定义在策略中传递的参数)

package com.ljh.oj.judge.strategy;import com.ljh.oj.judge.model.JudgeInfo;
import com.ljh.oj.model.entity.Question;
import com.ljh.oj.model.entity.QuestionSubmit;
import lombok.Builder;
import lombok.Data;import java.util.List;/*** 上下文(用于定义在策略中传递的参数)*/
@Data
@Builder
public class JudgeContext {/*** 这个是调用代码沙箱之后所返回的判题结果的信息*/private JudgeInfo judgeInfo;/*** 调用代码沙箱之后所返回的输出列表*/private List<String> outputList;/*** 题目信息*/private Question question;/*** 用户提交信息* 主要作用就是获取当前提交的语言给JudgeManager使用*/private QuestionSubmit questionSubmit;
}

下面问题就来了:

我们定义好了策略,我们怎么来调用哪一种策略嘞?

首先我们要肯定知道从这个QuestionSubmit中来获取对应用户提交的语言

我们上面总结过:

注解啊,缓存一个map,或者在工厂类中写if-else

我们这里就采用if-else,另外两种方式都用过了

/*** 判题模块简化封装* 对doJudge方法进行更上一层封装*/
@Service
public class JudgeManager {public JudgeInfo doJudge(JudgeContext judgeContext){String language = judgeContext.getQuestionSubmit().getLanguage();JudgeStrategy judgeStrategy = new DefaultJudgeStrategy();if(QuestionSubmitLanguageEnum.JAVA.getValue().equals(language)){judgeStrategy = new JavaLanguageJudgeStrategy();}return judgeStrategy.doJudge(judgeContext);}
}

这样就比较简单,只要在这个JudgeManager中添加代码即可

AI项目策略模式应用:

已经在上面都已经讲差不多了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1537505.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

视频语音转成文字用什么工具?这4款就很不错!

如今视频语音已经可以帮助我们在短时间内或去很多信息&#xff0c;在办公&#xff0c;学习教育&#xff0c;影视娱乐方面可以帮助我们高效的进行记录。当我们需要文字化的整理时&#xff0c;就会需要转换的工具。这片文章遍要罗列4款我在办公室用到的几款比较高效准确的视频语音…

原来石岩也有羊台山登山口

带娃探寻家附近的好玩的&#xff0c;娃问&#xff1a;石岩湿地公园的水从哪里来的&#xff1f;我说可能是羊台山吧&#xff0c;我们可以随着河流去找下啊。其实我内心也是没底的&#xff0c;我去的羊台山都是从龙华的羊台山东地铁下车去的。 骑行的好处就是可以松弛感看风景&a…

犬类检测系统源码分享

犬类检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

C++ | (二)类与对象(上)

燕子去了&#xff0c;有再来的时候&#xff1b;杨柳枯了&#xff0c;有再青的时候&#xff1b;桃花谢了&#xff0c;有再开的时候。但是&#xff0c;聪明的&#xff0c;你告诉我&#xff0c;我们的假期为什么一去不复返呢&#xff1f; 目录 一、初识类 1.1 类的定义 1.2 C中…

在petalinux工程里添加iperf

在petalinux工程里添加iperf 1,首先确定iperf包在哪里 xxx/components/yocto/layer/ meta-openembedded/meta-oe/recipes-benchmark/iperf3/ 2&#xff0c;然后就是往menuconfig中加&#xff1a; xxx/project-spec/meta-user/conf/user-rootfsconfig文件中 添加&#xff1a; …

maxcompute使用篇

文章目录 maxcompute使用篇1.mongoDB与maxcompute 进行数据同步1.1 基本类型的数据1.2部分复杂类型的数据 2.maxcompute中复杂数据类型解析2.1 get_json_object2.2 json_tuple2.3 处理json几种失效的情况:2.4 STR_TO_MAP、MAP_KEYS2.5 regexp_replace2.6 FROM_JSON2.7 nvl2.8 t…

高级I/O知识分享【epoll || Reactor ET,LT模式】

博客主页&#xff1a;花果山~程序猿-CSDN博客 文章分栏&#xff1a;Linux_花果山~程序猿的博客-CSDN博客 关注我一起学习&#xff0c;一起进步&#xff0c;一起探索编程的无限可能吧&#xff01;让我们一起努力&#xff0c;一起成长&#xff01; 目录 一&#xff0c;接口 epo…

ElasticSearch-2-核心语法集群高可用实战-Week2

ES批量操作 1.批量获取文档数据 这里多个文档是指&#xff0c;批量操作多个文档&#xff0c;搜索查询文档将在之后的章节讲解 批量获取文档数据是通过_mget的API来实现的 (1)在URL中不指定index和type 请求方式&#xff1a;GET 请求地址&#xff1a;_mget 功能说明 &#…

12 Java文件处理之写入、读取:IO流(中):高级流(缓冲流、转换流、序列化流和反序列化流、打印流)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、缓冲流1 字节缓冲流(1)BufferedInputStream:字节缓冲输入流构造方法---- BufferedInputStream(InputStream in):创建一个使用默认缓冲区大小的缓冲输入流。---- BufferedInputStream(In…

【浅水模型MATLAB】尝试复刻SCI论文中的溃坝流算例

【浅水模型MATLAB】尝试复刻SCI论文中的溃坝流算例 前言问题描述控制方程及数值方法浅水方程及其数值计算方法边界条件的实现 代码框架与关键代码模拟结果 更新于2024年9月17日 前言 这篇博客算是学习浅水方程&#xff0c;并利用MATLAB复刻Liang (2004)1中溃坝流算例的一个记录…

特殊文本文件日志技术重点笔记。

特殊文本文件&#xff0c;日志技术(黑马 一套入门 3h) 特殊文件 日志技术 把程序运行的信息&#xff0c;记录到文件中&#xff0c;方便程序员定位bug&#xff0c;并了解程序的执行情况等。 1.为什么要用这些特殊文件 1.1存储单个用户的: 用户名,密码 1.2存储多个用户的&…

数据清洗-缺失值填充-XGboost算法填充

目录 一、安装所需的python包二、采用XGboost算法进行缺失值填充2.1可直接运行代码2.2以某个缺失值数据进行实战2.2.1 代码运行过程截屏&#xff1a;2.2.2 填充后的数据截屏&#xff1a; 三、XGBoost算法原理介绍3.1 XGBoost 的定义3.2 XGBoost 的核心思想3.3 XGBoost 的特点3.…

2024 批量下载知乎回答/文章/想法/专栏/视频/收藏夹,导出 excel 和 pdf

之前分享过文章 2024批量下载知乎回答文章想法专栏收藏夹&#xff0c;公众号文章内容图片封面视频音频&#xff0c;微博内容图片视频评论转发数据&#xff0c;导出excel和pdf &#xff0c;今天再整理分享下知乎知乎回答/文章/想法/专栏/视频/收藏夹下载。 苏生不惑 这个账号已…

Jenkins基于tag的构建

文章目录 Jenkins参数化构建设置设置gitlab tag在工程中维护构建的版本按指定tag的版本启动服务 Jenkins参数化构建设置 选择参数化构建&#xff1a; 在gradle构建之前&#xff0c;增加执行shell的步骤&#xff1a; 把新增的shell框挪到gradle构建之前&#xff0c; 最后保存 …

驱动器磁盘未格式化难题:深度剖析与恢复实践

驱动器磁盘未格式化的深层探索 在数据存储与管理的日常中&#xff0c;驱动器作为我们数字生活的基石&#xff0c;其稳定性直接关系到数据的安全与可用性。然而&#xff0c;当屏幕上赫然出现“驱动器中的磁盘未被格式化”的提示时&#xff0c;许多用户往往感到手足无措&#xf…

Linux 文件与目录操作命令详解

文章目录 前言创建文件1. touch2. vim 文件内容显示3. cat4. more5. less6. head7. tail 文件&#xff08;目录&#xff09;复制、删除和移动8. cp9. rm10. mv 压缩文件与解压缩11. gzip12. zip 和 unzip 创建目录13. mkdir 删除目录14. rmdir 改变工作目录15. cd16. pwd 显示目…

【C语言】联合体枚举的讲解

目录 ✨声明&#xff01;&#xff01;&#xff01;&#xff1a; 联合体与结构体只有一个区别&#xff0c;那就是内存存储方式不同 &#x1f495;1.联合体的声明 &#x1f495;2.联合体内存的存储 &#x1f495;3.联合体字节大小的计算 例题2&#xff1a; ✨4.枚举的声明…

全面掌握 Jest:从零开始的测试指南(下篇)

在上一篇测试指南中&#xff0c;我们介绍了Jest 的背景、如何初始化项目、常用的匹配器语法以及钩子函数的使用。这一篇篇将继续深入探讨 Jest 的高级特性&#xff0c;包括 Mock 函数、异步请求的处理、Mock 请求的模拟、类的模拟以及定时器的模拟、snapshot 的使用。通过这些技…

list从0到1的突破

目录 前言 1.list的介绍 2.list的常见接口 2.1 构造函数&#xff08; (constructor)&#xff09; 接口说明 2.2 list iterator 的使用 2.3 list capacity 2.4 list element access 2.5 list modifiers 3.list的迭代器失效 附整套练习源码 结束语 前言 前面我们学习…

一款源码阅读的插件

文章目录 进度汇报功能预览添加高亮标记高亮风格设置笔记颜色设置数据概览高亮数据详情 结尾 进度汇报 之前提到最近有在开发一个源码阅读的IDEA插件&#xff0c;第一版已经开发完上传插件市场了&#xff0c;等官方审批通过就可以尝鲜了。插件名称&#xff1a;Mark source cod…