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

项目背景:

首先这篇文章是总结了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.rhkb.cn/news/430815.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vite分目录打包以及去掉默认的.gz 文件

1.vite打包情况介绍&#xff1a; 1.1vite在不进行任何配置的情况下&#xff0c;会将除开public的所有引用到资源打包编译添加哈希值至assets文件夹中&#xff08;非引用文件以及行内样式图片未被打包编译资源会被treeSharp直接忽略不打包&#xff09;&#xff0c;     1.2w…

七层负载均衡和四层负载均衡的区别

文章目录 什么是七层负载均衡&#xff1f;一、定义与工作原理二、优点与缺点三、应用场景四、常见七层负载均衡器五、负载均衡算法 什么是四层负载均衡&#xff1f;一、定义与原理定义&#xff1a;原理&#xff1a; 二、特点与应用场景特点&#xff1a;应用场景&#xff1a; 三…

Python中的数据可视化:从基础图表到高级可视化

数据可视化是数据分析和科学计算中不可或缺的一部分。它通过图形化的方式呈现数据&#xff0c;使复杂的统计信息变得直观易懂。Python提供了多种强大的库来支持数据可视化&#xff0c;如Matplotlib、Seaborn、Plotly等。本文将从基础图表入手&#xff0c;逐步介绍如何使用这些库…

基础漏洞——SSRF

目录 一.原理 二.引起ssrf的函数 三.这些函数具体作用 &#xff08;1&#xff09;File_get_content() &#xff08;2&#xff09;Fsockopen() &#xff08;3&#xff09;Curl_exec() 四.常见的业务场景&#xff08;可能出现的漏洞的地方&#xff0c;漏洞挖掘&#xff09…

自动化学习2:pytest的高级用法(mark标记/fixture/hook)

一.mark的用法 概念&#xff1a;Pytest提供的mark标记&#xff0c;允许我们标记测试函数&#xff0c;测试类和整个模块。通过不同的标记实现不同的运行策略&#xff0c;如标记冒烟测试用例。 1.注册标记 可以在pytest.ini文件注册自定义标记 除了自己注册的标记外&#xff0…

蓝队技能-应急响应篇Web内存马查杀JVM分析Class提取诊断反编译日志定性

知识点&#xff1a; 1、应急响应-Web内存马-定性&排查 2、应急响应-Web内存马-分析&日志 注&#xff1a;传统WEB类型的内存马只要网站重启后就清除了。 演示案例-蓝队技能-JAVA Web内存马-JVM分析&日志URL&内存查杀 0、环境搭建 参考地址&#xff1a;http…

Java面试篇基础部分- 锁详解

可重入锁 可重入锁也叫作递归锁,是指在同一个线程中,在外层函数获取到该锁之后,内存的递归函数还可以获取到该锁。在Java语言环境下,ReentrantLock和Synchroinzed都是可重入锁的代表。 公平锁与非公平锁 公平锁(Fair Lock)是指在分配锁之前检查是否有线程在排队等待获取…

搜维尔科技:SenseGlove触觉反馈手套遥操作人形机器人、机械臂解决方案

硬件组成&#xff1a; 1. SenseGlove 力反馈手套&#xff1a;这是整个系统的核心交互设备&#xff0c;手套上配备了多种传感器和执行器。传感器可以精确地捕捉用户手部的动作&#xff0c;包括手指的弯曲程度、手掌的朝向、手部的移动速度等信息。执行器则能够根据系统反馈的信…

SRS流媒体服务器在宝塔面板下的安装

目录 一、安装 1、安装Docker 2、安装srs 二、测试 1、进入后台 2、推流 3、播放测试: (1)网页 (2)拉流 之前一篇文章,我们介绍了SRS流媒体服务器在CentOS下的安装,安装流程还是比较麻烦且耗时的,其实SRS支持Docker部署,今天我们介绍在宝塔面板的Docker中部署…

[C#]winform 使用opencvsharp实现玉米粒计数

【算法介绍】 这段代码是使用OpenCvSharp库&#xff08;OpenCV的C#封装&#xff09;对图像进行处理&#xff0c;主要流程包括图像的二值化、腐蚀操作、距离变换、轮廓检测&#xff0c;并在原图上标出检测到的轮廓位置及数量。下面是对代码的详细解读&#xff1a; 初始化&…

element下拉框联动 或 多选 回显数据后页面操作不生效问题解决

第一种:多选回显不生效 解决方式: 代码: <el-form-item label"系统" prop"Key"> <el-select v-model"addForm.Key" multiple placeholder"请选择" change"$forceUpdate()"> <el-option v-for"item …

【计算机网络 - 基础问题】每日 3 题(十九)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

肝内胆管癌中三级淋巴结构分布与临床预后的相关性研究|文献精析·24-09-22

小罗碎碎念 这篇文章是关于肝内胆管癌&#xff08;intrahepatic cholangiocarcinoma, iCCA&#xff09;中三级淋巴结构&#xff08;tertiary lymphoid structures, TLSs&#xff09;的分布、密度及其对临床结果的预测价值的研究。 作者类型作者姓名单位名称&#xff08;中文&a…

如何在算家云搭建DiffSynth-Kolors-Painter(图像生成)

一、DiffSynth-Kolors-Painter简介 DiffSynth 画板提供了 Prompt 分区控制技术&#xff0c;可以通过创建图层、调整不同的提示&#xff08;Prompt&#xff09;精细地控制画面的每一部分&#xff0c;影响画面的特定区域&#xff0c;从而实现对画面的精细操控&#xff0c;实现了…

基于单片机的智能窗帘控制系统-设计说明书

设计摘要&#xff1a; 智能窗帘控制系统是一种利用单片机技术实现的智能化控制系统&#xff0c;可以实现窗帘的自动开合和定时控制功能。本系统的设计基于单片机技术&#xff0c;结合传感器、电机和执行器等硬件设备&#xff0c;实现对窗帘的智能化控制。通过传感器采集环境信…

从一个文本文件中挑选出符合条件的内容行

某天&#xff0c;张三得到一个需求&#xff0c;将如下格式的文本文件中的文件名开头的内容行提取出来&#xff0c;存入一个新的文本文件。 ok 0 文件名&#xff1a;1_zoukaige.mp3 index:10 文件名&#xff1a;2_dahan.mp3 index:20 文件名&#xff1a;3_kuai.mp3 index:30 文件…

LTE协议栈学习

1、高通Modem架构 LTE网络架构 3、LTE协议栈 1、 NAS协议栈: EPS Mobility Management (EMM) 支持UE中的移动功能 EPS Session Management (ESM) 支持在UE和PDN网关之间建立和维护IP连接 高通平台NAS层结构 根据3GPP TS 23.122描述&#xff0c; 自动搜网顺序如下 HPLMN EH…

Redisson 总结

1. 基础使用 1.1 引入依赖 <dependencies><dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId></dependency> </dependencies>包含的依赖如下 1.2 配置文件 其实默认主机就…

MCU自动测量单元采集振弦式应变计测值的过程

振弦式应变计是一种广泛应用于土木工程、地质勘探等领域的高精度传感器&#xff0c;用于测量结构的应变变化。近年来&#xff0c;随着微控制器单元(MCU)的发展&#xff0c;自动化测量技术得到了极大的提升&#xff0c;使得振弦式应变计的测值采集更加高效和精确。本文将详细介绍…

vue-router路由(重定向,嵌套,动态路由匹配,命名,高亮,守卫)

一、前端路由的概念与原理 路由router就是对应关系。分为前端路由和后端路由。 1后端路由 后端路由指的是&#xff1a;请求方式、请求地址与function处理函数之间的对应关系。在node.js中&#xff0c;express理由的基本用法如下&#xff1a; const express require(expres…