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

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

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

学习

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

更多

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

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
计算机科学入门/课时/设计模式与 UML
RawBlame
课时目录12 节

计算机科学入门

  1. 01计算机如何工作
  2. 02数据结构
  3. 03算法与搜索排序
  4. 04复杂度分析
  5. 05离散数学基础
  6. 06操作系统概念
  7. 07数据库原理
  8. 08计算机网络
  9. 09系统设计入门
  10. 10安全基础
  11. 11设计模式与 UML
  12. 12综合练习

设计模式与 UML

GoF 模式、类图、依赖注入。

30 分钟

设计模式与 UML

这一课你将能做到

  1. 识别创建型、结构型、行为型三类 GoF 模式的典型场景,写出单例、工厂、策略、模板方法、观察者的 Java 实现
  2. 看懂并画出简单 UML 类图:类、接口、继承、实现、组合、依赖六种关系
  3. 解释依赖注入(DI)与控制反转(IoC),说明 Spring 容器如何把模式「内置」进框架

为什么要学

设计模式不是炫技名词,而是「前辈反复踩坑后总结的标准结构」。当代码里出现这些信号:对象创建逻辑到处复制、新增一种判题语言要改五个文件、模块之间互相 new 死对方、状态变化要通知十个子系统——对应的模式已经在敲门了。

判题平台里模式密集:多语言编译执行(工厂 + 策略)、判题流程固定步骤(模板方法)、提交后触发统计与推送(观察者/事件)、配置类全局唯一(单例思想)。而 Spring 本身就是模式的集大成者:Bean 容器、依赖注入、AOP 代理、JdbcTemplate。学会模式,你读框架源码和设计可扩展代码的能力会同时上台阶。

先建立图像

GoF 23 模式三大类(先掌握每类最常用的)
创建型:把「对象怎么来」封装起来
  单例 Singleton   全局一个实例(日志/配置)
  工厂 Factory     屏蔽具体类型(按语言创建 Java/C++/Go 编译器)
  建造者 Builder   分步构造复杂对象(题目:描述+样例+限制)
结构型:把类/对象组合成更大结构
  适配器 Adapter   接口转换(新旧判题接口共存)
  装饰器 Decorator  不改正文加能力(IO 流、缓存包装)
  代理 Proxy       控制访问(Spring AOP、懒加载)
行为型:分配职责与通信
  策略 Strategy    一族算法可互换(多语言判题/多种限流算法)
  模板方法 Template 父类定骨架,子类填空
  观察者 Observer  状态变化自动通知(提交事件→统计+推送)

UML 关系强度(由弱到强)
依赖 ···▶(临时用一下)  关联 ──▶(持有)
聚合 ◇──▶(整体与部分,可分离)  组合 ◆──▶(同生共死)
实现 ──|▶(接口)        泛化 ──▷(继承)

跟着做

步骤 1:工厂 + 策略支撑多语言判题

// 策略接口:各种语言的执行逻辑可互换
interface JudgeStrategy {
    String language();
    String compile(String sourceFile);
    String runCommand();
}

class JavaJudge implements JudgeStrategy {
    public String language() { return "java"; }
    public String compile(String src) { return "javac " + src; }
    public String runCommand() { return "java Main"; }
}

class CppJudge implements JudgeStrategy {
    public String language() { return "cpp"; }
    public String compile(String src) { return "g++ -o main " + src; }
    public String runCommand() { return "./main"; }
}

// 工厂:根据用户选择的语言产出策略,调用方不认识具体类
class JudgeStrategyFactory {
    private final Map<String, JudgeStrategy> strategies = new java.util.HashMap<>();

    JudgeStrategyFactory(java.util.List<JudgeStrategy> all) {
        all.forEach(s -> strategies.put(s.language(), s));
    }

    JudgeStrategy create(String language) {
        JudgeStrategy strategy = strategies.get(language);
        if (strategy == null) {
            throw new IllegalArgumentException("暂不支持的语言: " + language);
        }
        return strategy;
    }
}

public class JudgeDemo {
    public static void main(String[] args) {
        JudgeStrategyFactory factory =
                new JudgeStrategyFactory(java.util.List.of(new JavaJudge(), new CppJudge()));

        JudgeStrategy strategy = factory.create("java");
        System.out.println(strategy.compile("Main.java"));
        System.out.println(strategy.runCommand());
    }
}

扩展新语言(如 GoJudge)只需新增一个类并注册,调用方零修改——这就是对扩展开放、对修改关闭(OCP)。如果不用策略,典型坏味道是一长串 if ("java".equals(lang)) {...} else if (...),每加语言都改主流程。

步骤 2:模板方法固定判题骨架

abstract class AbstractJudgeTemplate {

    // 模板:流程步骤固定(final 防止子类改顺序),差异步骤交给子类实现
    public final String judge(String source) {
        prepareWorkspace();
        String compileResult = compile(source);
        if (!compileResult.equals("OK")) return "CE";       // 编译失败
        String output = execute();
        return compare(output, expectedOutput()) ? "AC" : "WA";
    }

    private void prepareWorkspace() { System.out.println("准备隔离目录"); }

    protected abstract String compile(String source);
    protected abstract String execute();

    private String expectedOutput() { return "42"; }
    private boolean compare(String actual, String expected) {
        return actual.trim().equals(expected);
    }
}

class JavaJudgeTemplate extends AbstractJudgeTemplate {
    protected String compile(String source) { System.out.println("javac 编译"); return "OK"; }
    protected String execute() { System.out.println("JVM 运行"); return "42"; }
}

public class TemplateDemo {
    public static void main(String[] args) {
        System.out.println(new JavaJudgeTemplate().judge("class Main{}"));
    }
}

步骤 3:观察者(事件机制)解耦提交后的动作

import java.util.ArrayList;
import java.util.List;

interface SubmissionListener {
    void onResult(long submissionId, String status);
}

class StatListener implements SubmissionListener {
    public void onResult(long id, String status) {
        System.out.println("更新统计: " + id + " " + status);
    }
}

class PushListener implements SubmissionListener {
    public void onResult(long id, String status) {
        System.out.println("WebSocket 推送给用户: " + id);
    }
}

class SubmissionEventBus {
    private final List<SubmissionListener> listeners = new ArrayList<>();

    void subscribe(SubmissionListener listener) { listeners.add(listener); }

    void publish(long id, String status) {
        for (SubmissionListener listener : listeners) {
            listener.onResult(id, status);
        }
    }
}

public class ObserverDemo {
    public static void main(String[] args) {
        SubmissionEventBus bus = new SubmissionEventBus();
        bus.subscribe(new StatListener());
        bus.subscribe(new PushListener());
        bus.publish(1024, "AC");
        // 判题主流程不认识统计和推送,新增通知渠道只需加监听器 —— Spring 的
        // ApplicationEvent / @EventListener 就是这个模式的容器化版本
    }
}

步骤 4:单例与 UML 类图

// 现代单例写法:枚举天然防反射与反序列化重复创建
enum JudgeConfig {
    INSTANCE;

    private int timeLimitMs = 1000;
    public int getTimeLimitMs() { return timeLimitMs; }
}

public class SingletonDemo {
    public static void main(String[] args) {
        System.out.println(JudgeConfig.INSTANCE.getTimeLimitMs());
    }
}

对应 UML 类图(判题策略的结构图):

        ┌─────────────────────────┐
        │   «interface»           │
        │   JudgeStrategy         │
        │ +language():String      │
        └───────────△─────────────┘
                    │ implements(虚线空心三角)
        ┌───────────┴────────────┐
        │ JavaJudge              │
        │ +compile() +runCommand()│
        └────────────────────────┘
 JudgeStrategyFactory ──▶ JudgeStrategy   (依赖:虚线箭头)

记法:三角箭头指向父类/接口(继承用实线、实现用虚线);普通箭头表示持有或临时使用;菱形在整体一端(空心可分离=聚合,实心同生死=组合)。

讲清楚要点

  1. 模式是「问题-上下文-解法」的三元组,脱离场景套模式就是过度设计;判断标准是「变化点在哪」——对象类型常变用工厂,算法常换用策略,步骤稳定但实现可变用模板。
  2. 优先组合而非继承:装饰器、策略、代理都通过「持有另一个对象」获得灵活性,避免继承层次爆炸;这与 Effective Java 的原则一致。
  3. Spring 把模式框架化了:IoC 容器负责对象创建与装配(工厂+单例思想),构造器/Setter 注入是依赖注入,AOP 用动态代理实现,@EventListener 是观察者,JdbcTemplate/RestTemplate 用模板方法统一骨架。
  4. 依赖注入的价值:对象不再自己 new 依赖(控制反转),依赖从外部传入,替换实现(如把真实判题机换成 Mock)和单元测试都变得容易;配合面向接口编程,模块间只依赖抽象。
  5. UML 是沟通语言不是施工图纸:工作中画「足够表达设计的简图」即可,不必穷尽所有符号;时序图(对象间消息按时间排列)在梳理接口调用链时和类图一样常用。

你可能会懵的地方

  • 单例 Bean 为什么还能处理并发请求? 单例指容器里只有一个实例,多个线程并发调用它的方法;只要方法不使用共享可变成员(无状态),局部变量线程私有,就没有线程安全问题。这也是 Spring Service/Controller 默认为单例的前提。

  • 策略模式和工厂模式是不是一回事? 不是,常常配合:策略定义「可互换的行为」,工厂负责「选出/创建正确的策略」。一个解决行为差异,一个解决对象获取。

  • 代理、装饰器、适配器长得像,怎么分? 看意图:代理控制访问(不改变接口,加权限/延迟/日志);装饰器增强行为(层层包装,如 BufferedInputStream);适配器转换接口(让不兼容的类协同)。结构相似但目的不同。

  • 为什么不推荐继承实现复用? 继承在编译期固定、破坏封装(子类依赖父类实现细节)、层次深了难维护。组合在运行期可替换,更灵活;只有真正的「is-a」关系才用继承。

  • 一定要把 23 个模式全背下来吗? 不必。先吃透本课 8 个高频模式并在项目里用出 2-3 个,其余建立「知道它解决什么问题」的索引,需要时再查 Refactoring.Guru 或 GoF 原书。

错法对照

错法问题正解
字段注入 @Autowired 到处写依赖隐蔽、无法 final、测试难构造器注入(单构造器可省略注解)
双重检查锁漏写 volatile指令重排导致半初始化对象私有静态持有者或枚举单例
模板方法不加 final子类改顺序破坏骨架模板方法声明为 final
为用而用,简单逻辑硬套三层模式代码膨胀、可读性下降先确认变化点真实存在再引入
监听器里执行慢操作且不隔离异常一个监听器拖垮整个发布异步事件 + 异常隔离(Spring @Async/@EventListener)

卡点急救

  1. 新增一种类型要改很多文件:找到散落的类型判断,用工厂 + 策略收敛到「加一个类 + 注册」。
  2. 循环依赖启动报错:先反思设计(必要时用 @Lazy 或事件解耦),长期是重新划分模块边界,而不是长期靠注解掩盖。
  3. 模式用了但同事看不懂:类名体现意图(XxxStrategy/XxxFactory),包结构按角色归类,配一张 UML 简图与场景说明。

面试常问

  • 讲一个你在项目里用过的设计模式。 推荐 STAR 式回答:判题平台要支持多语言——用 JudgeStrategy 封装编译运行差异、工厂按 language 选择、Spring 自动收集所有实现注册;新增加语言不动主流程,也方便单元测试 Mock。能说出替代方案(长 if-else)的弊端更佳。

  • Spring 的 IoC 和 DI 是什么关系? IoC 是思想:对象不再自己控制依赖的创建与生命周期,反转给容器;DI 是实现手段:容器通过构造器/Setter/字段把依赖注入。配合面向接口编程,带来松耦合与可测试性。

  • 模板方法和策略有什么区别? 模板方法用继承:父类固定算法骨架,子类重写某些步骤(改动受骨架约束);策略用组合:整个算法族可整体互换,更灵活。选择看变化的是「个别步骤」还是「整体算法」。

自测三问

  1. 给你「新增 Python/Go 判题支持」的需求,从接口到注册完整画出类图与调用链。
  2. 不看资料,写出观察者模式三件套(事件源、监听接口、发布逻辑)并对应 Spring 注解。
  3. 组合、聚合、关联在 UML 上怎么画?代码层面如何区分?

小结一句话

设计模式是「封装变化点、靠组合松耦合」的经验结晶,UML 是让结构被团队看见的语言,而 Spring 把其中最常用的模式直接做进了容器——理解模式就是理解现代 Java 框架的设计语法。

延伸

  • Refactoring.Guru 设计模式(中文)——GoF 全模式的图解与 Java 示例,入门与速查俱佳
  • 《Head First 设计模式》官方页——直觉驱动的模式入门经典
  • GoF《设计模式:可复用面向对象软件的基础》——模式概念的开山权威原书
  • 下一课:综合练习:LRU 缓存——收官实战,用哈希表 + 双向链表把前 11 课的数据结构、复杂度与工程取舍整合到一个组件里。
加载本节练习…
← 上一课安全基础下一课 →综合练习