Skip to content

设计模式全景与选型:23 种怎么记,怎么用

本文是设计模式系统学习系列的 L1 入门篇。前置:1. SOLID 与设计原则:从坏味道说起。 学完可以配合面试题食用:无

23 种模式不是用来背的

翻开任何一本设计模式书,目录都是 23 个条目按创建型/结构型/行为型排列。新手往往试图记住每个模式的定义、UML 图、参与者角色,然后到实际写代码时一个也用不上。

问题出在起点上:人脑不适合按"名字"检索,适合按"问题"检索。你遇到"对象创建逻辑太分散"时,自然想知道怎么解决,而不是回忆"有个叫工厂模式的东西定义是什么"。

所以这篇不讲 UML,不讲 23 个各自的实现代码。先弄清三件事就够了:

  • 三类划分解决三类不同的问题
  • 工程里哪些模式真正高频,哪些冷门到可以无视
  • 什么时候该上模式,什么时候硬上模式是反模式

三大类:创建、结构、行为

GoF 把 23 种模式分成三类,这个分类本身就有指导意义——它告诉你代码出问题通常出在哪个环节。

创建型管"对象怎么生出来的"。当 new 关键字到处出现,或者对象构造逻辑太复杂(参数多、依赖多、条件分支多),创建型模式上场。工厂系列把"构建什么"从使用方剥离,单例保证全局只有一个实例,建造者把多参数构造拆成链式步骤。

结构型管"类/对象怎么搭在一起"。当代码的耦合表现为"一个改全都要改",或者类之间需要中间层才能对接,结构型模式起作用。适配器转换接口,代理控制访问,装饰器叠加功能,外观提供简化入口。

行为型管"类/对象之间怎么协作"。当控制流分散在多个 if-else 里,或者多个对象需要互相通知,行为型模式出手。策略把算法族抽成可替换的单元,模板方法固定骨架留扩展点,责任链把请求沿管道传递,观察者解耦事件发布者和订阅者。

下面这张选型地图把最常见的问题和对应的模式串起来:

mermaid
flowchart TD
    A[遇到什么问题?] --> B{对象创建}
    B --> C[创建逻辑散乱] --> D[工厂系列]
    B --> E[只需一个实例] --> F[单例]
    B --> G[多参数构建] --> H[建造者]
    A --> I{类如何组织}
    I --> J[接口不兼容] --> K[适配器]
    I --> L[控制第三方访问] --> M[代理]
    I --> N[功能叠加] --> O[装饰器]
    I --> P[简化子系统] --> Q[外观]
    A --> R{对象如何协作}
    R --> S[算法可替换] --> T[策略]
    R --> U[固定骨架+可变步骤] --> V[模板方法]
    R --> W[一对多通知] --> X[观察者]
    R --> Y[请求沿管道传递] --> Z[责任链]

这张图不用记,写代码的时候遇到"这个对象创建又分散了",回来扫一眼就行。

高频 Top 10 与冷门警示

据我观察,在 Java 后端工程里,以下 10 个模式的使用频率占到 90% 以上:

工厂、单例、策略、模板方法、代理、观察者、建造者、责任链、适配器、装饰器

剩下的十几种在干什么?访问者(Visitor)在编译器和 AST 解析里有用,日常业务代码几乎碰不到。解释器(Interpreter)更是只出现在 DSL 解析器实现里。中介者(Mediator)可以用消息队列替代,MVC 里的 Controller 本身已经承担了部分中介者角色。备忘录(Memento)在需要撤销操作的场景(如文本编辑器)才会出现,大部分后端服务不需要。

冷门不代表没用,但不要为了"学全"去硬套。面试问"访问者模式举例"能答出来就行,工程里遇到 AST 解析再查也不迟。

反模式警告:三行 if-else 硬拆成策略+工厂

模式最大的陷阱是过度设计。来看一个真实发生过的问题:

java
// 原始代码:三行,清晰
if (type == 1) {
    return a * 0.9;
} else {
    return a * 0.8;
}

// 重构后:8 个类,为了"可扩展"
interface DiscountStrategy { double calculate(double a); }
class VipDiscount implements DiscountStrategy { ... }
class NormalDiscount implements DiscountStrategy { ... }
class DiscountStrategyFactory {
    private Map<Integer, DiscountStrategy> map = new HashMap<>();
    public DiscountStrategyFactory() {
        map.put(1, new VipDiscount());
        map.put(2, new NormalDiscount());
    }
}
// 调用方
DiscountStrategy strategy = factory.getStrategy(type);
return strategy.calculate(a);

原来的三行代码,加了 8 个类后,逻辑没变,只多了一个"可扩展"的承诺。问题是这个"扩展"从来没有发生过——业务两年没加过新折扣类型。增加的 8 个类成了维护负担:新同事要看五个文件才能理解"打个折而已"。

模式什么时候该上? 当变化已经发生,或者你有确凿证据表明即将发生,而不是"万一以后要扩展"。两次重复才抽象,三次重复才上模式。先写清晰的 if-else,等它真的长到 20 个分支再重构,不丢人。

记忆法:按"我遇到的问题"记

如果非要用一种方式记住 23 种模式,推荐"问题-模式对照表",而不是"模式-定义对照表"。

遇到的问题适用的模式
创建对象的逻辑散落在各处工厂系列
一个类全局只能有一个实例单例
构造参数太多,可读性差建造者
算法需要在运行时替换策略
算法骨架固定,部分步骤可变模板方法
需要控制对某个对象的访问代理
给对象叠加功能,未来可能再加装饰器
接口不兼容,需要转换层适配器
给复杂子系统提供简单入口外观
一对多通知,解耦发布者和订阅者观察者
请求需要沿管道顺序处理责任链
把请求封装为对象,支持排队/撤销命令

写代码时遇到问题,看这张表。比背 23 个 UML 图好用十倍。

动手实操:过度设计反例对比

下面展示一个"该上模式"和"不该上模式"的对比,同一个校验逻辑的两种写法。

java
// 写法 A:过度设计,为模式而模式
// 场景:用户注册时校验手机号长度 > 3
interface Validator {
    boolean validate(String input);
}
class PhoneLengthValidator implements Validator {
    public boolean validate(String input) {
        return input.length() > 3;
    }
}
class ValidatorChain {
    private List<Validator> validators = new ArrayList<>();
    public void add(Validator v) { validators.add(v); }
    public boolean validate(String input) {
        for (Validator v : validators) {
            if (!v.validate(input)) return false;
        }
        return true;
    }
}
// 调用:ValidatorChain chain = new ValidatorChain();
// chain.add(new PhoneLengthValidator());
// chain.validate(phone);

// 写法 B:够用就停,必要的时候再拆
// 场景:用户注册校验,目前只有手机号长度 > 3 和格式检查
if (phone.length() <= 3) {
    return "手机号太短";
}
if (!phone.matches("^1[3-9]\\d{9}$")) {
    return "手机号格式不对";
}
// 等将来校验规则超过 5 条,再考虑拆成责任链

写法 B 12 行,写法 A 35 行。当校验规则只有 2-3 条时,写法 B 可读性更好、维护成本更低。等规则增加到 8-10 条,再考虑拆成责任链也不迟——而且那时候你有充分的业务数据支撑设计决策。

常见误区与小结

  • 误区 1:以为 23 种都要会手写。工程里 90% 场景只用到 10 种,冷门模式知道存在即可
  • 误区 2:模式一定要用接口。简单工厂可以是静态方法,单例可以是枚举,灵活使用
  • 误区 3:模式越多代码越"好"。每引入一个模式就增加一层抽象,抽象有成本
  • 误区 4:UML 图画得规范才算会用。设计模式的价值在于沟通,不在于画图
  • 误区 5:先设计模式再写代码。模式应该是重构的结果,不是设计的起点

小结:设计模式是工程经验,不是理论学问。如果写代码时考虑"该用什么模式",说明还没用对。正确的状态是写完后发现"这段代码看起来像工厂模式"——模式是事后识别,不是事前套用。下一篇进入 L2 核心,从工程里使用频率最高的工厂家族开始拆解。

参考

参考:GoF《设计模式:可复用面向对象软件的基础》第 1 章(分类法)、Martin Fowler《重构:改善既有代码的设计》第 3 章(坏味道清单)、Effective Java 第 1 条(静态工厂方法与构造器比较)

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。
粤ICP备2026104257号-1