判题机模块预开发(架构师)(工厂模式)
判题机模块
是为了把代码交个代码沙箱去处理 得到结果返回
代码沙箱
梳理判题模块和代码沙箱的关系
判题模块:调用代码沙箱 把代码和输入交给代码沙箱去执行
代码沙箱:只负责接受代码和输入 返回编译的结果 不负责判题
这两个模块完全解耦
我们采用API交互
为什么代码沙箱要接受和输出一组运行用例
前提:我们的每道题目有多组测试用例
如果每个用例单独调用一个代码用例 会调用多次接口 需要多次网络运输 程序要多次编译 记录程序的执行状态 重复的代码不重复编译
这是一种常见的性能优化的方法
创建一个新的包
用来放代码沙箱模块
先写一个接口
package com.dduo.dduoj.judge.codesandbox;
public interface CodeSandbox {
ExecuteCodeRequest executeCode(ExecuteCodeRequest executeCodeRequest);
}
提高通用性
之后我们的项目代码只调用接口
不调用具体的实现类
就不用去修改名称了 便于拓展
写一下实体类
ExecuteCodeRequest请求
package com.dduo.dduoj.judge.codesandbox.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExecuteCodeRequest {
private List<String> inputList;
private String code;
private String language;
}
ExecuteCodeResponse响应
package com.dduo.dduoj.judge.codesandbox.model;
import com.dduo.dduoj.model.dto.question.JudgeConfig;
import com.dduo.dduoj.model.dto.questionsubmit.JudgeInfo;
import java.util.List;
public class ExecuteCodeResponse {
private List<String> outputList;
//执行信息
private String message;
//执行状态
private Integer status;
private JudgeInfo judgeInfo;
}
完善
定义不同的代码沙箱实现类
示例代码沙箱
远程代码沙箱
第三方代码沙箱
架构工作
lombok Builder注解
测试一下
package com.dduo.dduoj.judge.codesandbox;
import com.dduo.dduoj.judge.codesandbox.impl.ExampleCodeSandbox;
import com.dduo.dduoj.judge.codesandbox.impl.RemoteCodeSandbox;
import com.dduo.dduoj.judge.codesandbox.model.ExecuteCodeRequest;
import com.dduo.dduoj.judge.codesandbox.model.ExecuteCodeResponse;
import com.dduo.dduoj.model.enums.QuestionSubmitLanguageEnum;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
class CodeSandboxTest {
@Test
void executeCode() {
CodeSandbox codeSandbox = new RemoteCodeSandbox();
String code = "int main() { }";
String language = QuestionSubmitLanguageEnum.JAVA.getValue();
List<String> inputList = Arrays.asList("1 2", "3 4");
ExecuteCodeRequest executeCodeRequest = ExecuteCodeRequest.builder()
.code(code)
.language(language)
.inputList(inputList)
.build();
ExecuteCodeResponse executeCodeResponse = codeSandbox.executeCode(executeCodeRequest);
Assertions.assertNotNull(executeCodeResponse);
}
}
工厂模式
但是现在问题是我们把new代码沙箱写死了 如果后面项目要改用其他沙箱
可能要改很多地方的代码
我们要使用工厂模式
根据用具传入的字符串参数 生成对应的代码沙箱实现类
package com.dduo.dduoj.judge.codesandbox;
import com.dduo.dduoj.judge.codesandbox.impl.ExampleCodeSandbox;
import com.dduo.dduoj.judge.codesandbox.impl.RemoteCodeSandbox;
import com.dduo.dduoj.judge.codesandbox.impl.ThirdPartyCodeSandbox;
//代码沙箱工厂 根据字符串参数 创建指定的代码沙箱示例
public class CodeSandboxFactory {
/*
* 创建代码沙箱示例
* @param type 沙箱类型
* @return
* */
public static CodeSandbox NewInstance(String type) {
switch (type) {
case "example":
return new ExampleCodeSandbox();
case "remote":
return new RemoteCodeSandbox();
case "thirdParty":
return new ThirdPartyCodeSandbox();
default:
return new ExampleCodeSandbox();
}
}
}
如果确定代码沙箱示例不会出现线程安全问题
可复用
那么可以使用单例工厂模式
但是这种方式是不可取的 我们应该把这些东西放到配置里面
配置化 去改配置文件 而不是修改字符串
这就叫参数配置化 开发者只需要去修改配置文件 而不是去看项目代码 就能自定义使用项目的更多功能
先在application.yml里面去设置
再在程序里面去读取
示例
package com.dduo.dduoj.judge.codesandbox;
import com.dduo.dduoj.judge.codesandbox.impl.ExampleCodeSandbox;
import com.dduo.dduoj.judge.codesandbox.impl.RemoteCodeSandbox;
import com.dduo.dduoj.judge.codesandbox.model.ExecuteCodeRequest;
import com.dduo.dduoj.judge.codesandbox.model.ExecuteCodeResponse;
import com.dduo.dduoj.model.enums.QuestionSubmitLanguageEnum;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
import java.util.List;
@SpringBootTest
class CodeSandboxTest {
@Value("${codesandbox.type:example}")
private String value;
@Test
void executeCode() {
CodeSandbox codeSandbox = CodeSandboxFactory.NewInstance(value);
String code = "int main() { }";
String language = QuestionSubmitLanguageEnum.JAVA.getValue();
List<String> inputList = Arrays.asList("1 2", "3 4");
ExecuteCodeRequest executeCodeRequest = ExecuteCodeRequest.builder()
.code(code)
.language(language)
.inputList(inputList)
.build();
ExecuteCodeResponse executeCodeResponse = codeSandbox.executeCode(executeCodeRequest);
Assertions.assertNotNull(executeCodeResponse);
}
}
我们要增强代码沙箱的能力
在调用代码沙箱前 输出请求参数 在代码沙箱调用后 输出响应结果日志
package com.dduo.dduoj.judge.codesandbox.impl;
import com.dduo.dduoj.judge.codesandbox.CodeSandbox;
import com.dduo.dduoj.judge.codesandbox.model.ExecuteCodeRequest;
import com.dduo.dduoj.judge.codesandbox.model.ExecuteCodeResponse;
import lombok.extern.slf4j.Slf4j;
//示例代码沙箱 (仅供测试 跑通业务流程)
@Slf4j
public class ExampleCodeSandbox implements CodeSandbox {
@Override
public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
log.info("请求信息"+executeCodeRequest.toString());
System.out.println("示例代码沙箱");
return null;
}
}
思考
我们每一个代码沙箱类都写一个 log.info ?
难道每次调用代码沙箱前后都要执行log ?
我们使用代理模式 提供一个Proxy 来增强代码沙箱的能力
静态代理模式
中介
调用者调用代理类 代理类去调用代码沙箱
代理类还可以做一些额外的功能
不仅不用改变原本的代码沙箱实现类 而且对调用者来说 基本也没有改变
也不需要在每一个调用代码沙箱的地方去统计代码
package com.dduo.dduoj.judge.codesandbox;
import com.dduo.dduoj.judge.codesandbox.model.ExecuteCodeRequest;
import com.dduo.dduoj.judge.codesandbox.model.ExecuteCodeResponse;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@AllArgsConstructor
public class CodeSandboxProxy implements CodeSandbox{
private CodeSandbox codeSandbox;
@Override
public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
log.info("代码沙箱的请求信息"+executeCodeRequest.toString());
ExecuteCodeResponse executeCodeResponse = codeSandbox.executeCode(executeCodeRequest);
log.info("代码沙箱的响应信息"+executeCodeResponse.toString());
return executeCodeResponse;
}
}
接下来我们就可以去修改调用方式
@Test
void executeCodeByProxy() {
CodeSandbox codeSandbox = CodeSandboxFactory.NewInstance(value);
codeSandbox =new CodeSandboxProxy(codeSandbox);
String code = "int main() { }";
String language = QuestionSubmitLanguageEnum.JAVA.getValue();
List<String> inputList = Arrays.asList("1 2", "3 4");
ExecuteCodeRequest executeCodeRequest = ExecuteCodeRequest.builder()
.code(code)
.language(language)
.inputList(inputList)
.build();
ExecuteCodeResponse executeCodeResponse = codeSandbox.executeCode(executeCodeRequest);
Assertions.assertNotNull(executeCodeResponse);
}