先举个例子:您需要一件衣服,可以直接从工厂里面提货,而不用去管这件衣服是怎么做出来的,以及这件衣服里面的具体由什么材料和工艺来实现。
这种设计思想就是工厂模式,我们使用对象时就直接通过工厂来获取对象,工厂提供了一种创建对象的最佳方式。在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。
意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
主要解决:主要解决接口选择的问题。
何时使用:我们明确地计划不同条件下创建不同实例时。
如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。
优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。
缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。
Spring中的BeanFactory就是使用了工厂模式
1、日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方。 2、数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时。 3、设计一个连接服务器的框架,需要三个协议,"POP3"、"IMAP"、"HTTP",可以把这三个作为产品类,共同实现一个接口。
我们的系统中有个判题模块,需要调用代码沙箱来运行用户的代码,代码沙箱是一个接口,有多种实现:
接口:
import com.zhangzq.quanzioj.judge.codesandbox.model.ExecuteCodeRequest;
import com.zhangzq.quanzioj.judge.codesandbox.model.ExecuteCodeResponse;
import org.springframework.stereotype.Component;
/**
* 代码沙箱接口
*/
@Component
public interface CodeSandBox {
/**
* 执行代码
* @param executeCodeRequest
* @return
*/
ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest);
}
实现:
/**
* 示例代码沙箱
*/
public class ExampleCodeSandBoxImpl implements CodeSandBox {
@Override
public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
List inputList = executeCodeRequest.getInputList();
JudgeInfo judgeInfo = new JudgeInfo();
judgeInfo.setMessage(JudgeInfoMessageEnum.ACCEPTED.getValue());
judgeInfo.setTime(100L);
judgeInfo.setMemory(100L);
ExecuteCodeResponse executeCodeResponse = ExecuteCodeResponse.builder()
.outputList(inputList)
.message("测试执行成功")
.judgeInfo(judgeInfo)
.build();
executeCodeResponse.setStatus(QuestionSubmitStatusEnum.SUCCESS.getValue());
return null;
}
}
/**
* 远程代码沙箱
*/
public class RemoteCodeSandBoxImpl implements CodeSandBox {
// 定义鉴权请求头
public static final String AUTH_REQUEST_HEADER = "auth";
public static final String AUTH_REQUEST_SECRET = "auth";
@Override
public ExecuteCodeResponse executeCode(ExecuteCodeRequest executeCodeRequest) {
System.out.println("远程代码沙箱");
String url = "http://localhost:8090/executeCode";
String bodyJson = JSONUtil.toJsonStr(executeCodeRequest);
String responseStr = HttpUtil.createPost(url)
.body(bodyJson)
.header(AUTH_REQUEST_HEADER, AUTH_REQUEST_SECRET)
.execute()
.body();
if (StringUtils.isBlank(responseStr)) {
throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "executeCode remoteSandbox error, message = " + responseStr);
}
// todo 异常信息处理
return JSONUtil.toBean(responseStr, ExecuteCodeResponse.class);
}
}
在我们的判题模块业务代码中想要调用代码沙箱,传统的方式是new一个接口实现类的实例,如:
@SpringBootTest
class CodeSandboxTest {
@Test
void executeCode() {
CodeSandbox codeSandbox = new RemoteCodeSandbox();
String code = "int main() { }";
String language = QuestionSubmitLanguageEnum.JAVA.getValue();
List 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的实现类,可能还需要改其他很多地方,很不方便,这时候我们就可以使用工厂模式,来用工厂创建代码沙箱实例,可根据传递的参数创建对应的代码沙箱:
代码沙箱工厂:
/**
* 代码沙箱工厂(根据字符串参数创建指定的代码沙箱实例)
*/
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();
}
}
}
可以将传递的参数弄成配置,在配置文件中定义,以后要修改代码沙箱直接修改配置文件就可以了,如:
@Value("${codesandbox.type:example}")
private String type;
@Test
void executeCodeByValue() {
CodeSandBox codeSandBox = CodeSandBoxFactory.newInstance(type);
String code = "int main() { }";
String language = QuestionSubmitLanguageEnum.JAVA.getValue();
List 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);
}