CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
软件设计与架构入门/课时/设计模式
RawBlame
课时目录12 节

软件设计与架构入门

  1. 01整洁代码
  2. 02设计原则
  3. 03面向对象
  4. 04编程范式
  5. 05设计模式
  6. 06架构原则
  7. 07架构风格
  8. 08架构模式
  9. 09领域驱动设计
  10. 10企业模式
  11. 11综合架构练习
  12. 12路线图对照复习

设计模式

GoF 23 模式概览。

30 分钟

设计模式:GoF 在判题平台的实战

这一课你将能做到

  1. 说出 23 个 GoF 模式的三大分类(创建型、结构型、行为型),并举出每类在判题平台的代表实现
  2. 用工厂、策略、观察者、模板方法、责任链五个高频模式改造判题平台核心流程
  3. 判断"这里该不该上模式",避免过度设计

为什么要学

设计模式是前人踩坑后总结的"可复用解决方案"。判题平台里,提交校验、多语言判题、结果通知、限流熔断,每一步都对应一个经典模式。学会模式不是背类图,而是看到问题时能立刻反应:"这是策略模式该上的地方。"

先建立图像

创建型          结构型           行为型
─────────      ─────────       ─────────
工厂           适配器           策略
建造者         装饰器           观察者
原型           代理             模板方法
单例           门面             责任链
                              命令

跟着做

步骤 1:工厂模式——创建不同语言的判题器

public interface JudgeEngine {
    JudgeResult judge(Submission submission);
}

@Component
public class JudgeEngineFactory {
    private final Map<String, JudgeEngine> engines;

    public JudgeEngineFactory(List<JudgeEngine> engineList) {
        this.engines = engineList.stream()
            .collect(Collectors.toMap(JudgeEngine::language, Function.identity()));
    }

    public JudgeEngine create(String language) {
        JudgeEngine engine = engines.get(language.toLowerCase());
        if (engine == null) throw new IllegalArgumentException("不支持的语言: " + language);
        return engine;
    }
}

工厂隐藏了"到底 new 哪个实现"的细节,调用方只说"我要 Java 判题器"。

步骤 2:策略模式——多种限流算法

public interface RateLimitStrategy {
    boolean tryAcquire(String key);
}

@Component
public class TokenBucketStrategy implements RateLimitStrategy {
    public boolean tryAcquire(String key) { /* 令牌桶实现 */ }
}

@Component
public class SlidingWindowStrategy implements RateLimitStrategy {
    public boolean tryAcquire(String key) { /* 滑动窗口实现 */ }
}

@Service
public class SubmitRateLimiter {
    private final RateLimitStrategy strategy;
    public SubmitRateLimiter(@Qualifier("tokenBucket") RateLimitStrategy strategy) {
        this.strategy = strategy;
    }
    public boolean allow(long userId) {
        return strategy.tryAcquire("submit:" + userId);
    }
}

限流算法可插拔,改配置就换实现,业务代码不动。

步骤 3:观察者模式——判题结果通知

public interface JudgeResultListener {
    void onJudgeFinished(Submission submission);
}

@Component
public class WebSocketNotifyListener implements JudgeResultListener {
    public void onJudgeFinished(Submission s) { /* 推送 WebSocket */ }
}

@Component
public class PracticeRecordListener implements JudgeResultListener {
    public void onJudgeFinished(Submission s) { /* 更新 cf_practice_record */ }
}

@Service
public class JudgeEventPublisher {
    private final List<JudgeResultListener> listeners;
    public JudgeEventPublisher(List<JudgeResultListener> listeners) {
        this.listeners = listeners;
    }
    public void publish(Submission submission) {
        listeners.forEach(l -> l.onJudgeFinished(submission));
    }
}

判题完成是"事件",监听者各自处理,publisher 不知道也不关心有多少监听者。

步骤 4:模板方法——判题流程骨架

public abstract class AbstractJudgeEngine implements JudgeEngine {
    @Override
    public final JudgeResult judge(Submission submission) {
        preCheck(submission);
        CompileResult compile = doCompile(submission);
        if (!compile.success()) return JudgeResult.compileError(compile.error());
        RunResult run = doRun(compile, submission);
        return postProcess(run);
    }

    protected abstract void preCheck(Submission s);
    protected abstract CompileResult doCompile(Submission s);
    protected abstract RunResult doRun(CompileResult compile, Submission s);
    protected abstract JudgeResult postProcess(RunResult run);
}

@Component
public class JavaJudgeEngine extends AbstractJudgeEngine {
    protected void preCheck(Submission s) { /* Java 特有检查 */ }
    protected CompileResult doCompile(Submission s) { /* javac */ }
    protected RunResult doRun(CompileResult c, Submission s) { /* java 运行 */ }
    protected JudgeResult postProcess(RunResult run) { /* 对比输出 */ }
}

模板方法把"判题流程"固定,把"具体实现"留给子类。Java 和 Python 判题器共用骨架。

步骤 5:责任链——提交前置校验

public abstract class SubmissionValidator {
    private SubmissionValidator next;
    public SubmissionValidator linkWith(SubmissionValidator next) {
        this.next = next; return next;
    }
    public ValidationResult validate(Submission s) {
        ValidationResult r = doValidate(s);
        if (!r.ok() || next == null) return r;
        return next.validate(s);
    }
    protected abstract ValidationResult doValidate(Submission s);
}

@Component
public class AuthValidator extends SubmissionValidator {
    protected ValidationResult doValidate(Submission s) {
        return s.getUserId() == null ? ValidationResult.fail("未登录") : ValidationResult.ok();
    }
}

@Component
public class CodeValidator extends SubmissionValidator {
    protected ValidationResult doValidate(Submission s) {
        return s.getCode().isBlank() ? ValidationResult.fail("代码为空") : ValidationResult.ok();
    }
}

校验器像链条一样串起来,每个节点独立,可插拔、可复用。

讲清楚要点

  1. 模式是"问题-解决方案"对:不是背类图,是看到问题能想到模式。
  2. 创建型管对象怎么生:工厂、建造者、单例、原型。
  3. 结构型管对象怎么组:适配器、装饰器、代理、门面。
  4. 行为型管对象怎么协作:策略、观察者、模板方法、责任链、命令。
  5. Spring 让模式零成本:@Component + List<Interface> 注入,策略和观察者几乎不用手动维护注册表。
  6. 模式不是银弹:能 if-else 解决的别上策略;能直接调用的别上观察者。复杂度要匹配问题规模。

你可能会懵的地方

  • 策略和工厂怎么一起用? 工厂负责"创建",策略负责"选择"。判题平台里,工厂创建具体语言的 JudgeEngine,策略模式让 JudgeEngine 本身可替换。两者经常搭档。
  • 观察者模式和消息队列什么关系? 观察者是进程内同步调用,消息队列是跨进程异步。判题结果先用观察者同步更新本地状态,再发 MQ 异步通知其他服务,两者互补。
  • 模板方法和策略模式怎么选? 模板方法是"流程固定、步骤可变"(继承);策略是"算法整体替换"(接口)。判题流程固定用模板方法,限流算法整体替换用策略。
  • 责任链和拦截器/过滤器什么区别? 责任链是"一个节点处理不了就传给下一个";拦截器是"前后都处理,可中断"。Spring 的 HandlerInterceptor 是拦截器,Servlet Filter 是过滤器,都算责任链的变体。
  • 23 个模式都要会吗? 不需要。面试和实战高频的是:工厂、策略、观察者、模板方法、责任链、单例、代理、适配器。其他的了解概念即可。

错法对照

错法问题正解
所有逻辑塞一个 Service违反 SRP,难测难改拆成策略、观察者、责任链
new JavaJudgeEngine() 硬编码无法替换实现工厂 + Spring 注入
判题完成直接调 5 个 service高耦合观察者发布事件,监听者各自处理
每个语言复制粘贴判题流程重复代码模板方法固定骨架,子类填差异
校验规则写死在 Service 里改规则要改核心代码责任链,每个校验独立

卡点急救

  1. 不知道用哪个模式:画个流程图,看变化点在哪——创建变化用工厂,算法变化用策略,流程变化用模板,通知变化用观察者。
  2. Spring 里模式写多了类爆炸:用 @Configuration + @Bean 集中注册,或用 @ComponentScan 自动扫描;接口 + List 注入让 Spring 帮你组装。
  3. 模式叠加导致过度设计:问"这个扩展点未来 6 个月会变吗?"不会就保持简单;会就先用最简单的模式(策略/观察者),别一次上全套。

面试常问

  • 工厂模式和策略模式区别? 答:工厂管"怎么创建对象"(生产),策略管"怎么替换算法"(使用)。判题平台里,工厂创建 JudgeEngine,策略让 RateLimitStrategy 可插拔。工厂通常配合策略一起用。
  • 观察者模式在 Spring 里怎么实现? 答:Spring 的 ApplicationEvent + @EventListener 就是观察者。判题完成发 SubmissionJudgedEvent,监听者用 @EventListener 处理。也可以用 List<Listener> 手动注入,更轻量。
  • 模板方法模式在 JDK 或 Spring 里的例子? 答:AbstractApplicationContext 的 refresh() 是模板方法,onRefresh() 留给子类;JdbcTemplate 的 execute() 是模板方法,doInStatement() 留给回调。判题平台的 AbstractJudgeEngine 同理。

自测三问

  1. 判题平台要支持"提交前进行用户限流",应该用什么模式?为什么不用 if-else?
  2. 判题完成后要同时更新练习记录、发 WebSocket 通知、写审计日志,应该用什么模式?
  3. 新增"Go 语言判题"时,工厂、策略、模板方法各自怎么配合?

小结一句话

设计模式是"变化点"的封装术——把会变的创建、算法、流程、通知抽出来,让不变的骨架稳定。

延伸

  • 《设计模式:可复用面向对象软件的基础》GoF
  • 《Head First 设计模式》
  • Spring Framework 事件机制
  • 下一课:架构原则——耦合、内聚与组件边界。
加载本节练习…
← 上一课编程范式下一课 →架构原则