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

news2024/11/15 12:24:05

项目背景:

首先这篇文章是总结了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;

    @Override
    public 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<>();

    @PostConstruct
    private 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中

 具体调用代码:
/**
     *  第四版代码
     *  利用代理模式对调用沙箱功能进行优化
     *  调用前和调用后自动打上日志
     */
    @Test
    void 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 {


    @Resource
    private 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, "应用配置有误,未找到匹配的策略");
    }
}

代码逻辑分析:

    @Resource
    private 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.coloradmin.cn/o/2152519.html

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

相关文章

Gitlab学习(007 gitlab项目操作)

尚硅谷2024最新Git企业实战教程&#xff0c;全方位学习git与gitlab 总时长 5:42:00 共40P 此文章包含第25p-第p26的内容 文章目录 推送项目到gitlabidea安装gitlab插件配置免密登录推送项目到远程库 在gitlab上创建项目额外功能的使用推送分支到远程库标记功能创建合并请求 推…

leetcode刷题(71-75)

算法是码农的基本功&#xff0c;也是各个大厂必考察的重点&#xff0c;让我们一起坚持写题吧。 遇事不决&#xff0c;可问春风&#xff0c;春风不语&#xff0c;即是本心。 我们在我们能力范围内&#xff0c;做好我们该做的事&#xff0c;然后相信一切都事最好的安排就可以啦…

IM项目组件学习-----spdlog二次封装

这里写自定义目录标题 封装的原因封装的思想初始化接口的封装对日志输出接口进行宏的封装 封装的原因 1.避免单例的锁冲突&#xff0c;因此直接创建全局的线程安全的日志器进行使用 2.因为日志输出没有文件名行号&#xff0c;因此使用宏进行二次封装输出日志的文件名和行号 3.…

【shell脚本1】Shell脚本学习--入门

目录 简介 Hello World 注释 打印输出 简介 Shell是一种脚本语言&#xff0c;那么&#xff0c;就必须有解释器来执行这些脚本。 Unix/Linux上常见的Shell脚本解释器有bash、sh、csh、ksh等&#xff0c;习惯上把它们称作一种Shell。我们常说有多少种Shell&#xff0c;其实说的…

昇腾大模型推理解决方案MindIE部署

MindIE大模型推理套件 MindIE&#xff08;Mind Inference Engine&#xff0c;昇腾推理引擎&#xff09;是华为公司针对AI全场景推出的整体解决方案&#xff0c;包含丰富的推理加速套件。通过开放各层次AI能力&#xff0c;支撑客户多样化的AI业务需求&#xff0c;使能百模千态&a…

Windows快捷切换Java jdk版本

使用方法 新建txt文本文件&#xff0c;将下方代码粘贴进去编辑对应的jdk路径 如&#xff1a;set JAVA_HOMED:\Java\jdk-17.0.11修改文件后缀为bat&#xff0c;双击运行选择对应版本 echo off rem 切换Java jdk版本 echo 请以管理员来进行切换 java -version:menu echo echo 请…

2024华为杯C题详细完整思路和视频讲解

文章目录 一、背景问题描述数据描述问题问题一&#xff1a; 励磁波形分类问题二&#xff1a; 斯坦麦茨方程&#xff08;Steinmetz-equation&#xff09;修正问题三&#xff1a; 磁芯损耗因素分析问题四问题五 参考文献补充磁芯损耗分离模型磁芯损耗经验计算模型 特别注意事项问…

【Android Studio】2024.1.1最新版本AS调试老项目(老版AS项目文件、旧gradle)导入其他人的项目

文章目录 实验环境开始修改项目文件1. 删除.gradle及.idea两个文件夹2.修改SDK路径&#xff08;本地SDK存放路径&#xff09;3.修改gradle版本4.修改gradle插件版本&#xff08;AGP&#xff09;5.修改JDK版本 实验环境 Android Studio 版本 项目版本 开始修改项目文件 1. 删…

我的AI工具箱Tauri版-MicrosoftTTS文本转语音

本教程基于自研的AI工具箱Tauri版进行MicrosoftTTS文本转语音服务。 MicrosoftTTS文本转语音服务 是自研的AI工具箱Tauri版中的一款功能模块&#xff0c;专为实现高效的文本转语音操作而设计。通过集成微软TTS服务&#xff0c;用户可以将大量文本自动转换为自然流畅的语音文件…

创新驱动,技术引领:2025年广州见证汽车电子技术新高度

汽车行业的创新浪潮正汹涌澎湃&#xff0c;一场引领未来出行的科技盛宴即将拉开帷幕&#xff01; AUTO TECH 2025 第十二届广州国际汽车电子技术展览会将于 2025 年 11 月 20日至 22 日在广州保利世贸博览馆&#xff08;PWTC Expo&#xff09;隆重举行。 作为亚洲地区领先的汽…

树莓派智能语音助手实现音乐播放

树莓派语音助手从诞生的第一天开始&#xff0c;我就想着让它能像小爱音箱一样&#xff0c;可以语音控制播放音乐。经过这些日子的倒腾&#xff0c;今天终于实现了。 接下里&#xff0c;和大家分享下我的实现方法&#xff1a;首先音乐播放模块用的是我在上一篇博文写的《用sound…

netty编程之基于websocket发送二进制数据

写在前面 本文看下基于websocket发送二进制数据。 1&#xff1a;正文 直接看源码吧&#xff0c;主要如下几个类&#xff1a; WebSocketServerProtocolHandler (内置)&#xff1a;负责websocket握手消息处理 BinaryWebSocketFrameHandler(自定义)&#xff1a;负责处理二进制…

【深入理解SpringCloud微服务】了解微服务的熔断、限流、降级,手写实现一个微服务熔断限流器

【深入理解SpringCloud微服务】了解微服务的熔断、限流、降级&#xff0c;手写实现一个微服务熔断限流器 服务雪崩熔断、限流、降级熔断降级限流 手写实现一个微服务熔断限流器架构设计代码实现整体逻辑ProtectorAspect#aroundMethod(ProceedingJoinPoint)具体实现1、获取接口对…

在 Windows 上运行 Vue 项目时解决 ‘NODE_OPTIONS‘ 错误

在 Windows 上运行 Vue 项目时解决 ‘NODE_OPTIONS’ 错误 在 Windows 系统上启动 Vue 项目时&#xff0c;遭遇报错。具体报错信息如下&#xff1a; ‘NODE_OPTIONS‘ 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件。这个错误通常意味着 Windows 系统无法识…

速通LLaMA2:《Llama 2: Open Foundation and Fine-Tuned Chat Models》全文解读

文章目录 概览LLaMA和LLaMA2的区别AbstractIntroductionPretrainingFine-tuning1. 概括2、Supervised Fine-Tuning&#xff08;SFT&#xff09;3、⭐Reinforcement Learning with Human Feedback&#xff08;RLHF&#xff09;&#x1f53a;总览Training Objectives&#xff1a;…

Redis——redispluspls库——通用命令以及String类型相关接口使用

文章目录 通用命令get&#xff0c;setkeys插入迭代器 expire和ttltype string 类型接口set和getset NX和XXmset 和 mgetgetrange 和 setrangeincr 和 decr 通用命令 get&#xff0c;set void get_set_test(sw::redis::Redis& redis){//bool set(const sw::redis::StringV…

回归预测 | Matlab实现INFO-HKELM向量加权算法优化混合核极限学习机多变量回归预测

回归预测 | Matlab实现INFO-HKELM向量加权算法优化混合核极限学习机多变量回归预测 目录 回归预测 | Matlab实现INFO-HKELM向量加权算法优化混合核极限学习机多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现INFO-HKELM向量加权算法优化混合核极…

使用Python和Proxy302代理IP高效采集Bing图片

目录 项目背景一、项目准备环境配置 二、爬虫设计与实现爬虫设计思路目标网站分析数据获取流程 代码实现1. 初始化爬虫类&#xff08;BingImageSpider&#xff09;2. 创建存储文件夹3. 获取图像链接4. 下载图片5. 使用Proxy302代理IP6. 主运行函数 运行截图 三、总结 项目背景 …

2024/9/21 leetcode 19题 24题

目录 19.删除链表中的倒数第n个结点 题目描述 题目链接 解题思路与代码 24.两两交换链表中的结点 题目描述 题目链接 解题思路与代码 19.删除链表中的倒数第n个结点 题目描述 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示…

Kyutai开源实时语音对话模型Moshi

新闻 法国人工智能实验室Kyutai在巴黎举行的一次活动上推出了能够进行自然交互的对话式人工智能助手Moshi&#xff0c;并计划将其作为开源技术发布。Kyutai表示&#xff0c;Moshi是首款可公开访问的人工智能助手&#xff0c;可实现实时对话&#xff0c;有别于OpenAI的GPT-4o&a…