利用OJ判題的多語言優雅解耦方法深入體會模板方法模式、策略模式、工廠模式的妙用

在線評測系統(Online Judge, OJ)的核心是判題引擎,其關鍵挑戰在于如何高效、安全且可擴展地支持多種編程語言。在博主的項目練習過程中,借鑒了相關設計模式實現一種架構設計方案,即通過組合運用模板方法、策略、工廠等設計模式,將判題流程中與語言相關的邏輯進行深度解耦,從而構建一個符合“開閉原則”、易于維護和擴展的現代化判題引擎。

1. 問題域分析:判題引擎的復雜性

一個典型的判題流程包含以下階段:

  1. 環境準備:創建隔離的執行環境(如 Docker 容器)。
  2. 代碼編譯:將源代碼編譯成可執行文件(非解釋型語言)。
  3. 代碼執行:在受限的環境中運行代碼,并監控資源消耗。
  4. 結果收集:獲取程序的輸出、錯誤、執行時間及內存消耗。
  5. 環境清理:銷毀執行環境,回收資源。

該流程的復雜性源于不同編程語言在“編譯”和“執行”階段的顯著差異:

  • 編譯型語言 (C++, Java): 需要特定的編譯器和編譯指令,生成中間產物(可執行文件或字節碼)。
  • 解釋型語言 (Python, JavaScript): 無需編譯,直接通過解釋器執行。
  • 運行參數: 不同語言的運行時(Runtime)在內存限制、安全策略等方面有不同的配置方式。

若采用過程式編程,通過大量的 if-elseswitch-case 語句來處理不同語言,將導致代碼結構僵化、維護成本高昂,每新增一門語言都可能引發對核心代碼的大規模修改,違背了軟件設計的 “開閉原則“ (Open-Closed Principle)。

2. 架構設計:設計模式的組合應用

為了應對上述挑戰,我們需要采用一系列設計模式來重構判題引擎,將流程中的“不變”與“可變”部分分離。(對于一些生產級別的安全配置本文進行忽略,著重于設計模式的使用)對于實現多語言的容器池參考:j借助線程池的思想,構建一個高性能、配置驅動的Docker容器池)

2.1. 模板方法模式:定義流程骨架

模板方法模式是整個架構的基石。我們定義一個抽象基類 AbstractJudgeTemplate,它封裝了判題流程的固定算法骨架。

@Component  
public abstract class AbstractJudgeTemplate {  @Autowired  protected MultiLanguageDockerSandBoxPool sandBoxPool;  // 模板方法,定義了判題的完整流程骨架  public final SandBoxExecuteResult judge(String userCode, List<String> inputList,Long timeLimit) {  // 1. 準備環境  String containerId = prepareEnvironment();  // 2. 創建用戶代碼文件  String userCodePath = createUserCodePath(containerId);  File userCodeFile = createUserCodeFile(userCode, userCodePath);  try {  // 3. 編譯代碼  CompileResult compileResult = compileCodeByDocker(containerId, userCodePath); // 傳遞所需參數  if (!compileResult.isCompiled()) {  // 如果編譯失敗,也需要清理文件和容器  return SandBoxExecuteResult.fail(CodeRunStatus.COMPILE_FAILED, compileResult.getExeMessage());  }  // 4. 運行代碼  return executeCodeByDocker(containerId, inputList,timeLimit); // 傳遞所需參數  }  catch (SecurityException e) {log.error("代碼安全檢查失敗: {}", e.getMessage());return SandBoxExecuteResult.fail(CodeRunStatus.SECURITY_ERROR, "代碼包含不安全內容");} catch (ContainerNotAvailableException e) {log.error("容器資源不足: {}", e.getMessage());judgeMetrics.recordContainerError(getLanguageType());return SandBoxExecuteResult.fail(CodeRunStatus.SYSTEM_ERROR, "系統資源不足,請稍后重試");} catch (Exception e) {log.error("判題過程發生異常", e);judgeMetrics.recordSystemError(getLanguageType());return SandBoxExecuteResult.fail(CodeRunStatus.SYSTEM_ERROR, "系統內部錯誤");} finally {  // 5. 清理環境  deleteUserCodeFile(userCodeFile);  cleanupEnvironment(containerId);  }  }  private void deleteUserCodeFile(File userCodeFile) {  if (userCodeFile != null && userCodeFile.exists()) {  FileUtil.del(userCodeFile);  }  }  /**  * 創建用戶代碼文件  */  private File createUserCodeFile(String userCode, String userCodePath) {  if (FileUtil.exist(userCodePath)) {  FileUtil.del(userCodePath);  }  return FileUtil.writeString(userCode, userCodePath, Constants.UTF8);  }  private void cleanupEnvironment(String containerId) {  // 只有在 containerId 有效時才歸還 ,還可以進行其他校驗if (containerId != null) {  sandBoxPool.returnContainer(containerId);  }  }//安全檢查的相關方法省略...  // --- 抽象方法 (鉤子),由子類實現 ---  /**  * 編譯代碼,不同語言實現不同  */  protected abstract CompileResult compileCodeByDocker(String containerId, String userCodePath);  /**  * 運行代碼,不同語言的運行命令和參數不同*/  protected abstract SandBoxExecuteResult executeCodeByDocker(String containerId, List<String> inputList,Long timeLimit);  /**  * 準備環境  * @return 容器id  */    protected abstract String prepareEnvironment();  protected abstract String createUserCodePath(String containerId);  
}

通過這種方式,AbstractJudgeTemplate 定義了“做什么”(判題流程),而將“怎么做”(具體語言的編譯和運行)的責任下放給了子類。

2.2. 策略模式:封裝語言特定邏輯

每個具體的語言實現都可以看作是一種獨立的“策略”。我們為每種支持的語言創建一個繼承自 AbstractJudgeTemplate 的具體類。

Java 策略實現:

@Service("java")  
public class JavaJudgeStrategy extends AbstractJudgeTemplate {  //與docker操作的對象,也可以進行二次封裝進行對上層提高封裝后的api@Autowired  private DockerClient dockerClient;  @Autowiredprivate LanguageProperties languageProperties;@Override  protected CompileResult compileCodeByDocker(String containerId, String userCodePath) {  // 從配置中獲取編譯命令String compileCmd = languageProperties.getJava().getCompileCmd();// 使用該命令在容器中執行編譯...log.info("Executing compile command: {}", compileCmd);// ... 省略與沙箱交互的底層代碼return CompileResult.success();}  @Override  protected SandBoxExecuteResult executeCodeByDocker(String containerId, List<String> inputList,Long timeLimit) {  // 從配置中獲取運行命令String executeCmd = languageProperties.getJava().getExecuteCmd();List<String> outputList = new ArrayList<>();for (String input : context.getInputList()) {// 拼接輸入參數并執行...log.info("Executing run command: {} with input: {}", executeCmd, input);// ... 省略與沙箱交互的底層代碼}//封裝結果   return getSanBoxResult(inputList, outList, maxMemory, maxUseTime); }  @Override  protected String prepareEnvironment() {  return sandBoxPool.getContainer(ProgramType.JAVA);  }  @Override  protected String createUserCodePath(String containerId) {  String codeDir = sandBoxPool.getHostCodeDir(containerId);  return codeDir + File.separator + //從配置中讀取也可以JudgeConstants.USER_CODE_JAVA_CLASS_NAME;  }//其他方法這里忽略
}

Python 策略實現:

@Service("python3")
public class PythonJudgeStrategy extends AbstractJudgeTemplate {@Autowiredprivate LanguageProperties languageProperties;@Overrideprotected CompileResult compileCodeByDocker(String containerId, String userCodePath) {// 解釋型語言,編譯步驟為空實現,直接返回成功return CompileResult.success();}@Overrideprotected SandBoxExecuteResult executeCodeByDocker(String containerId, List<String> inputList,Long timeLimit) {// env.executeCommand(RUN_CMD)// ... 返回運行結果}//其他重寫方法
}

現在,每種語言的判題邏輯被隔離在獨立的策略類中,實現了高度的內聚和解耦。

2.3. 工廠模式:動態選擇策略

有了各種策略,我們需要一個機制來根據客戶端請求(例如,任務中指定的語言)動態地選擇并實例化正確的策略。工廠模式是解決此問題的理想選擇。

結合 Spring 框架的依賴注入(DI),可以實現一個高效的策略工廠。

@Component
public class JudgeStrategyFactory {private final Map<String, AbstractJudgeTemplate> strategyMap;/*** 利用 Spring 的構造函數注入,自動將所有 AbstractJudgeTemplate 類型的 Bean 注入。* Key 為 Bean 的名稱 (e.g., "java", "python3"),Value 為 Bean 實例。*/@Autowiredpublic JudgeStrategyFactory(Map<String, AbstractJudgeTemplate> strategyMap) {this.strategyMap = strategyMap;}public AbstractJudgeTemplate getStrategy(String language) {AbstractJudgeTemplate strategy = strategyMap.get(language);if (strategy == null) {//可以自定義拋出業務異常throw new ServiceException(ResultCode.FAILED_NOT_SUPPORT_PROGRAM);}return strategy;}
}

客戶端調用:

@Service  
@Slf4j  
public class JudgeServiceImpl implements IJudgeService {    @Autowired  private JudgeStrategyFactory judgeStrategyFactory;  @Autowired  private UserSubmitMapper userSubmitMapper;  @Override  public UserQuestionResultVO doJudgeJavaCode(JudgeSubmitDTO judgeSubmitDTO) {  //獲取判題策略對象  AbstractJudgeTemplate strategy = judgeStrategyFactory.getStrategy(judgeSubmitDTO.getProgramType().getDesc());  //調用容器池進行判題  SandBoxExecuteResult sandBoxExecuteResult =  strategy.judge(judgeSubmitDTO.getUserCode(), judgeSubmitDTO.getInputList(),judgeSubmitDTO.getTimeLimit());  UserQuestionResultVO userQuestionResultVO = new UserQuestionResultVO();  //返回判題結果  //成功  //...相關判斷//失敗  //...相關判斷//存儲用戶代碼數據到數據庫  log.info("判題邏輯結束,判題結果為: {} ", userQuestionResultVO.getPass());  return userQuestionResultVO;  }
}

3. 架構優勢與可擴展性

通過上述設計模式的組合應用,我們構建了一個結構清晰、易于擴展的判題引擎:

  • 高內聚,低耦合: 每種語言的實現細節被封裝在各自的策略類中,與主流程和其他語言實現完全解耦。
  • 符合開閉原則:
    • 對修改關閉: 核心判題流程 AbstractJudgeTemplate 和調度器 JudgeDispatcherService 無需任何修改。
    • 對擴展開放: 若要新增對 Go 語言的支持,只需完成兩步:
      1. 創建一個 GoJudgeStrategy 類,繼承 AbstractJudgeTemplate 并實現其 compilerun 方法。
      2. 為該類添加 @Component("go") 注解。
        系統即可自動集成新的語言支持,無需改動任何已有代碼。
  • 職責單一: 每個類(模板、策略、工廠)的職責都非常明確,提升了代碼的可讀性和可維護性。

4. 結論

在復雜的系統設計中,直接的思考過程實現往往會導致僵化的、難以維護的系統。這時候不妨先對系統中每個類的職責先進行分析清楚,然后借助相關設計模式的思路,將業務邏輯進行解耦合,達到可拓展,可維護的系統架構。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/92696.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/92696.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/92696.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[FOC電機控制]霍爾傳感器于角度問題

如果電機有1對極(p1&#xff0c;那么每旋轉一圈的機械角度&#xff0c;電氣角度會轉動一圈&#xff08;360&#xff09;。如果電機有2對極(p2&#xff0c;那么每旋轉一圈的機械角度&#xff0c;電氣角度會轉動兩圈&#xff08;720&#xff09;。

阿里云 Flink

阿里云 Flink 是阿里云基于Apache Flink打造的企業級實時計算平臺&#xff0c;旨在為用戶提供高效、穩定、易用的流處理與批處理能力&#xff0c;幫助企業快速構建實時數據處理鏈路&#xff0c;支撐實時業務決策。核心特性流批一體計算繼承 Apache Flink “流批一體” 的核心優…

企業級高性能web服務器

1 web服務基礎 1.1 正常情況的單次web服務訪問流程&#xff1a; 正常情況下&#xff0c;單次 Web 服務訪問流程從用戶在客戶端發起請求開始&#xff0c;到最終在客戶端展示內容結束&#xff0c;涉及客戶端、網絡傳輸、服務器端等多個環節&#xff0c;以下是詳細過程&#xff…

免費PDF編輯軟件 pdf24-creator 及其安裝包

最近發現了一款還算是不錯的PDF編輯和閱讀軟件 pdf24-creator&#xff0c;官方下載網站為&#xff1a;https://tools.pdf24.org/zh/creator&#xff0c;但是官方下載如果沒有魔法的話&#xff0c;下載速度很慢&#xff0c;比百度網盤下載還滿&#xff0c;因此我把它分享到網盤。…

openvela之ADB

ADB&#xff08;Android Debug Bridge&#xff09;是一款功能豐富的命令行工具&#xff0c;旨在實現開發工作站與設備&#xff08;如模擬器、實體設備&#xff09;之間的通信。通過 ADB&#xff0c;開發者可以便捷地在設備上執行命令、傳輸文件、調試應用等。本文將詳細介紹 AD…

如何控制需求交付節奏

有效控制需求的交付節奏&#xff0c;其核心在于將產品開發過程從一個不可預測的、時快時慢的混亂狀態&#xff0c;轉變為一套產出穩定、流程順暢、步調可持續的系統化交付機制。要成功構建這套機制&#xff0c;實現有節奏的價值交付&#xff0c;必須綜合運用五大關鍵策略&#…

匯編中常用寄存器介紹

X86-32位寄存器 4個數據寄存器&#xff1a;EAX、EBX、ECX和EDX; 2個變址和指針寄存器&#xff1a;ESI和EDI; 2個指針寄存器&#xff1a;ESP和EBP; 1個指令指針寄存器&#xff1a;EIP; 6個段寄存器&#xff1a;ES、CS、SS、DS、FS和GS; 1個標志寄存器&#xff1a;EFlags。 在X8…

SOMGAN:用自組織映射改善GAN的模式探索能力

論文信息 論文題目:Improving mode exploring capability ofgenerative adversarial nets by self-organizing map(利用自組織映射提高生成對抗網絡的模式探索能力) 期刊:Neurocomputing 摘要:生成對抗網絡(GANs)的出現將生成模型的研究推向了一個新的高潮。支持這一進步…

《匯編語言:基于X86處理器》第12章 復習題和練習

本篇記錄了《匯編語言&#xff1a;基于X86處理器》第12章 復習題和練習的筆記。12.6復習題和練習12.6.1 簡答題1.假設有二進制浮點數1101.01101&#xff0c;如何將其表示為十進制分數之和?答&#xff1a;1101.01101(1x)(1x)(0x)(1x)(0x)(1x)(1x)(1x)(1x) 13.406252.為什么十進…

ApacheCon Asia 2025 中國開源年度報告:Apache Doris 國內第一

上周剛落下帷幕的 ApacheCon Asia 2025 中&#xff0c;一個數據讓所有人都為之震撼&#xff1a;全球 Apache 基金會項目 OpenRank 排行榜中&#xff0c;Apache Doris 位居第二&#xff0c;在中國 Apache 項目中更是穩居第一。 這個排名意味著什么&#xff1f;在 Apache 基金會管…

Pytest中實現自動生成測試用例腳本代碼

&#x1f345; 點擊文末小卡片&#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快在Python的測試框架中&#xff0c;我們通常會針對某個系統進行測試用例的維護&#xff0c;在對龐大系統進行用例維護時&#xff0c;往往會發現很多測試用例是差不多…

一周學會Matplotlib3 Python 數據可視化-標注 (Annotations)

鋒哥原創的Matplotlib3 Python數據可視化視頻教程&#xff1a; 2026版 Matplotlib3 Python 數據可視化 視頻教程(無廢話版) 玩命更新中~_嗶哩嗶哩_bilibili 課程介紹 本課程講解利用python進行數據可視化 科研繪圖-Matplotlib&#xff0c;學習Matplotlib圖形參數基本設置&…

安全合規1--實驗:ARP欺騙、mac洪水攻擊、ICMP攻擊、TCP SYN Flood攻擊

一、實驗環境 (思科的云實驗平臺)攻擊機&#xff1a;Kali Linux&#xff08;IP&#xff1a;192.168.234.128&#xff0c;MAC&#xff1a;00:00:29:35:64:EC&#xff09;目標1&#xff1a;網關&#xff08;IP&#xff1a;192.168.234.2&#xff0c;MAC&#xff1a;00:50:56:ED:D…

Linux下GCC的C++實現Hive到Snowflake數據遷移

程序結構 ├── main.cpp ├── config.json ├── hive_export/ ├── parquet_data/ ├── sql_scripts/ └── logs/核心代碼實現 (main.cpp) #include <iostream> #include <fstream> #include <vector> #include <thread> #include <mut…

drippingblues靶機教程

一、信息搜集首先將其在VirtualBOX中安裝&#xff0c;并將kali與靶機都設置為橋接模式緊接著我們掃描IP&#xff0c;來發現靶機地址&#xff0c;經過搜集&#xff0c;發現IP是192.168.1.9&#xff0c;我們去訪問一下緊接著我們掃一下開放了哪些端口。發現開放了21、22以及80端口…

39.【.NET8 實戰--孢子記賬--從單體到微服務--轉向微服務】--擴展功能--調整發布腳本

這篇文章&#xff0c;我們要調整發布腳本。之所以要調整發布腳本&#xff0c;是因為現在我們的項目有三個環境&#xff1a;本地&#xff08;Local&#xff09;、開發&#xff08;Development&#xff09;、生產&#xff08;Production&#xff09;。Tip&#xff1a;我們的項目雖…

商品、股指及ETF期權五檔盤口Tick級與分鐘級歷史行情數據多維解析

在金融數據分析領域&#xff0c;本地CSV文件是存儲高頻與低頻數據的常用載體。本文以期權市場數據為例&#xff0c;探討如何基于CSV格式處理分鐘級行情、高頻Tick數據、日頻數據、逐筆委托記錄、五檔訂單簿及歷史行情數據&#xff0c;并提供專業的技術實現方案。以下將從數據預…

云端軟件工程智能代理:任務委托與自動化實踐全解

云端軟件工程智能代理&#xff1a;任務委托與自動化實踐全解 背景與未來趨勢 隨著軟件工程復雜度不斷提升&#xff0c;開發者對自動化工具的依賴也日益增強。我們正進入一個“人機協作”的新時代&#xff0c;開發者可以專注于核心創新&#xff0c;將重復性、繁瑣的任務委托給智…

making stb style lib(1): do color print in console

col.h: see origin repo // origin repo: https://github.com/resyfer/libcol #ifndef _COL_HOL_H_ #define _COL_HOL_H_#include <stdlib.h> #include <stdio.h> #include <stdbool.h> #include <string.h> #include <math.h> // 新增&#xf…

llm本地部署+web訪問+交互

要實現基于llm的web訪問和交互&#xff0c;需支持對llm的訪問和對網絡搜索的調用。 這里使用ollama llm兼容openai sdk訪問&#xff1b;使用proxyless-llm-websearch模擬網絡搜索。 1 ollama本地部署 假設ollama已經部署&#xff0c;具體過程參考 在mac m1基于ollama運行dee…