Skip to content

设计模式从零到精通验收清单

设计模式不能学成“背名字、背 UML、背标准定义”。真正能在商业项目里用好设计模式,靠的是判断力:先看变化点,再看不抽象会怎样,最后判断某个模式是否真的能减少修改面、降低风险、提高可测试性。

本页按“是什么、为什么、怎么工作、不这样会怎样、商业场景、代码 Demo、常见坑、排查、面试”的标准,把创建型、结构型、行为型模式串成一套可验收的课程清单。

最终学习目标

学完设计模式模块,至少要能做到:

能力合格标准不合格表现
识别变化点能说清哪里会变、哪里稳定上来就问“用哪个模式”
判断是否需要模式能解释不用模式会怎样,用了有什么代价简单逻辑也抽一堆接口
创建型模式能区分单例、工厂、建造者把工厂写成业务大杂烩
结构型模式能区分代理、装饰器、适配器只知道“都是包一层”
行为型模式能区分策略、模板方法、观察者、责任链用策略消灭所有 if
Spring/JDK 应用能指出 Spring AOP、Bean 工厂、事件、Java IO 背后的模式只会背业务 Demo
商业落地能在支付、订单、导入、医院接口、权限、消息通知中选型模式和场景对不上
生产排查能排查策略选错、责任链卡住、代理失效、事件丢失模式一多就看不懂链路
面试表达能先讲业务问题,再讲模式和边界只背定义,不讲为什么

总学习路线

mermaid
flowchart TD
    A["识别变化点"] --> B["判断是否值得抽象"]
    B --> C["创建型:对象如何创建"]
    B --> D["结构型:对象如何组合增强"]
    B --> E["行为型:流程规则如何协作"]
    C --> F["单例、工厂、建造者"]
    D --> G["代理、装饰器、适配器"]
    E --> H["策略、观察者、模板方法、责任链"]
    F --> I["商业场景组合"]
    G --> I
    H --> I
    I --> J["排查、边界、面试表达"]

设计模式学习顺序不是先背 23 种模式,而是先问四个问题:

  1. 这个业务哪里会变化?
  2. 这种变化会不会持续发生?
  3. 如果不抽象,新增需求要改多少旧代码?
  4. 如果抽象,会不会让代码更难读、更难排查?

阶段一:设计模式到底解决什么

是什么

设计模式是对常见设计问题的经验总结。它的核心不是“让代码高级”,而是隔离变化点,让稳定部分少改,让变化部分可扩展。

为什么需要

没有设计意识时,代码常见演化过程是:

mermaid
flowchart TD
    A["第一个需求:直接写 if"] --> B["第二个需求:再加 else"]
    B --> C["第三个需求:复制一份改一改"]
    C --> D["第四个需求:多人同时改同一大方法"]
    D --> E["线上问题:漏改、错改、无法测试"]

设计模式试图把变化隔离出来:

mermaid
flowchart TD
    A["稳定主流程"] --> B["依赖抽象接口"]
    B --> C["变化实现 A"]
    B --> D["变化实现 B"]
    B --> E["变化实现 C"]
    F["新增需求"] --> G["新增实现类"]
    G --> B

不这样会怎样

问题后果
大量 if/else新增规则容易影响旧规则
业务层直接 new 具体实现替换实现困难,单测困难
横切逻辑散落日志、权限、事务重复且不一致
流程和步骤混在一起主流程越来越长
多供应商接口直接写在业务中每接一个供应商都改核心代码

阶段二:什么时候不要用设计模式

设计模式不是越多越好。

场景建议
只有一个实现,短期不会扩展先直接写清楚
业务规则还在频繁试错先保持简单,等稳定后再抽象
抽象后类暴增但修改面没减少不值得
新人读代码需要跳十几层可能过度设计
模式只是为了“看起来专业”不要用

一个实用判断:

mermaid
flowchart TD
    A["看到重复或 if"] --> B{"变化是否真实存在"}
    B -- "否" --> C["保持简单"]
    B -- "是" --> D{"变化是否会持续发生"}
    D -- "否" --> C
    D -- "是" --> E{"抽象是否减少修改面"}
    E -- "否" --> C
    E -- "是" --> F["选择合适模式"]

阶段三:创建型模式

创建型模式解决“对象怎么创建、谁来创建、创建过程如何统一”的问题。

单例模式

单例保证某个类在一个范围内只有一个实例。常见范围包括 JVM 级单例和 Spring 容器级单例。

mermaid
flowchart TD
    A["调用方"] --> B["getInstance 或 Spring 容器"]
    B --> C{"实例是否存在"}
    C -- "否" --> D["创建实例"]
    C -- "是" --> E["返回已有实例"]
    D --> E

适合:

  1. 配置管理器。
  2. 线程池管理器。
  3. 连接池。
  4. Spring 默认单例 Bean。

不适合:

  1. 保存用户请求状态。
  2. 保存可变业务上下文。
  3. 需要多租户隔离的数据。

错误示例:

java
public class BadUserContext {
    private static final BadUserContext INSTANCE = new BadUserContext();
    private Long currentUserId;

    public static BadUserContext getInstance() {
        return INSTANCE;
    }
}

currentUserId 是请求级数据,放进全局单例会导致多线程串数据。

工厂模式

工厂封装“选择哪个实现”和“如何获取对象”。

mermaid
flowchart TD
    A["业务输入 channel"] --> B["工厂 PayFactory"]
    B --> C{"选择支付实现"}
    C -- "ALI" --> D["AliPayService"]
    C -- "WECHAT" --> E["WechatPayService"]
    C -- "BANK" --> F["BankPayService"]
    D --> G["统一 PayService 接口"]
    E --> G
    F --> G

Spring Bean Map 工厂 Demo:

java
public interface PayService {
    String channel();

    void pay(PayRequest request);
}
java
@Component
public class PayServiceFactory {
    private final Map<String, PayService> serviceMap;

    public PayServiceFactory(List<PayService> services) {
        this.serviceMap = services.stream()
            .collect(Collectors.toMap(PayService::channel, service -> service));
    }

    public PayService get(String channel) {
        PayService service = serviceMap.get(channel);
        if (service == null) {
            throw new IllegalArgumentException("不支持的支付渠道: " + channel);
        }
        return service;
    }
}

工厂不应该负责扣库存、改订单、发消息。它只负责获取对象,业务流程仍放在 Service。

建造者模式

建造者解决“复杂对象参数多、可选字段多、默认值多、校验多”的问题。

java
public class ExportTask {
    private final String taskName;
    private final String fileType;
    private final List<String> fields;
    private final boolean async;

    private ExportTask(Builder builder) {
        if (builder.taskName == null || builder.taskName.isBlank()) {
            throw new IllegalArgumentException("任务名不能为空");
        }
        this.taskName = builder.taskName;
        this.fileType = builder.fileType == null ? "xlsx" : builder.fileType;
        this.fields = List.copyOf(builder.fields);
        this.async = builder.async;
    }

    public static class Builder {
        private String taskName;
        private String fileType;
        private List<String> fields = new ArrayList<>();
        private boolean async = true;

        public Builder taskName(String taskName) {
            this.taskName = taskName;
            return this;
        }

        public Builder fileType(String fileType) {
            this.fileType = fileType;
            return this;
        }

        public Builder fields(List<String> fields) {
            this.fields = fields;
            return this;
        }

        public ExportTask build() {
            return new ExportTask(this);
        }
    }
}

Lombok @Builder 只是生成代码,不自动保证业务合法性。必要校验仍要写在构造、工厂方法或 build 逻辑里。

阶段四:结构型模式

结构型模式解决“对象如何组合、增强、兼容”的问题。

代理模式

代理强调访问控制或横切增强。调用方不直接访问目标对象,而是先经过代理。

mermaid
flowchart TD
    A["调用方"] --> B["代理对象"]
    B --> C["权限、事务、日志、缓存"]
    C --> D["目标对象"]
    D --> E["返回结果"]
    E --> B

Spring 事务就是典型代理:

  1. 外部调用进入代理对象。
  2. 代理开启事务。
  3. 调用目标方法。
  4. 成功提交,异常回滚。

同类自调用事务失效,是因为没有经过代理对象。

装饰器模式

装饰器强调“动态叠加能力”,典型例子是 Java IO。

mermaid
flowchart TD
    A["FileInputStream"] --> B["BufferedInputStream"]
    B --> C["DataInputStream"]
    C --> D["调用方读取增强后的能力"]

代理和装饰器结构都可能包一层,但意图不同:

对比代理装饰器
关注点控制访问、横切增强叠加能力
调用方是否关心增强能力通常不关心通常关心组合后的能力
典型场景Spring AOP、远程代理Java IO、响应包装

适配器模式

适配器解决“已有接口不兼容,但能力想复用”的问题。

mermaid
flowchart TD
    A["统一 HospitalClient"] --> B["医院 A 适配器"]
    A --> C["医院 B 适配器"]
    A --> D["医院 C 适配器"]
    B --> E["HTTP + XML"]
    C --> F["HTTP + JSON"]
    D --> G["WebService"]

医疗接口适配器 Demo:

java
public interface HospitalClient {
    PatientReport queryReport(String patientId);
}
java
@Component
public class HospitalAClientAdapter implements HospitalClient {
    private final HospitalAApi hospitalAApi;

    public HospitalAClientAdapter(HospitalAApi hospitalAApi) {
        this.hospitalAApi = hospitalAApi;
    }

    @Override
    public PatientReport queryReport(String patientId) {
        HospitalAResponse response = hospitalAApi.query(patientId);
        return new PatientReport(
            response.getReportNo(),
            response.getPatientName(),
            response.getItems()
        );
    }
}

适配器主要做协议、字段、错误码转换,不要把复杂业务规则塞进去。

阶段五:行为型模式

行为型模式解决“多个对象之间如何协作、流程如何拆分、规则如何扩展”的问题。

策略模式

策略解决“同一件事有多种做法,并且做法会独立变化”。

mermaid
flowchart TD
    A["业务请求"] --> B["提取 strategyKey"]
    B --> C["策略工厂"]
    C --> D{"找到策略?"}
    D -- "否" --> E["抛异常或默认策略"]
    D -- "是" --> F["执行具体策略"]

通知策略 Demo:

java
public interface NotifyStrategy {
    String type();

    void send(NotifyMessage message);
}
java
@Component
public class SmsNotifyStrategy implements NotifyStrategy {
    @Override
    public String type() {
        return "SMS";
    }

    @Override
    public void send(NotifyMessage message) {
        // 调短信服务
    }
}

策略不是为了消灭所有 if。少量稳定判断直接写清楚即可。

观察者模式

观察者解决“一件事发生后,通知多个本地扩展点”。

mermaid
flowchart TD
    A["订单支付成功"] --> B["发布本地事件"]
    B --> C["发优惠券监听器"]
    B --> D["写审计日志监听器"]
    B --> E["站内信监听器"]

本地事件适合进程内扩展。跨服务可靠通知不要用普通观察者替代 MQ,因为 MQ 提供持久化、重试、消费确认、堆积处理。

Spring 事件 Demo:

java
public record OrderPaidEvent(Long orderId, Long userId) {
}
java
@Component
public class CouponListener {
    @EventListener
    public void onOrderPaid(OrderPaidEvent event) {
        // 发放优惠券
    }
}

如果监听器失败是否影响主流程,要在业务上明确。关键通知一般不要只靠同步本地事件。

模板方法模式

模板方法解决“流程顺序稳定,但某些步骤变化”。

mermaid
flowchart TD
    A["模板方法 execute"] --> B["读取文件"]
    B --> C["解析数据"]
    C --> D["校验数据"]
    D --> E["保存数据"]
    E --> F["生成结果"]

数据导入模板 Demo:

java
public abstract class AbstractImportTemplate<T> {
    public final ImportResult importData(InputStream inputStream) {
        List<T> rows = parse(inputStream);
        validate(rows);
        save(rows);
        return new ImportResult(rows.size());
    }

    protected abstract List<T> parse(InputStream inputStream);

    protected void validate(List<T> rows) {
        if (rows.isEmpty()) {
            throw new IllegalArgumentException("导入数据为空");
        }
    }

    protected abstract void save(List<T> rows);
}

模板方法通常依赖继承。继承关系不自然时,优先考虑组合、策略或责任链。

责任链模式

责任链解决“多个处理器按顺序处理同一个请求”。

mermaid
flowchart TD
    A["订单创建请求"] --> B["参数校验"]
    B --> C["库存校验"]
    C --> D["价格校验"]
    D --> E["风控校验"]
    E --> F["创建订单"]

订单校验链 Demo:

java
public interface OrderCheckHandler {
    void check(OrderCreateContext context);
}
java
@Component
@Order(10)
public class StockCheckHandler implements OrderCheckHandler {
    @Override
    public void check(OrderCreateContext context) {
        if (context.getStock() <= 0) {
            throw new IllegalStateException("库存不足");
        }
    }
}
java
@Component
public class OrderCheckChain {
    private final List<OrderCheckHandler> handlers;

    public OrderCheckChain(List<OrderCheckHandler> handlers) {
        this.handlers = handlers;
    }

    public void check(OrderCreateContext context) {
        for (OrderCheckHandler handler : handlers) {
            handler.check(context);
        }
    }
}

责任链必须管理顺序、异常策略和日志。链路过长且无日志时,会比普通代码更难排查。

阶段六:商业项目组合用法

场景常见组合原理
支付渠道工厂 + 策略工厂根据渠道找策略,策略执行支付
订单创建责任链 + 策略校验链有顺序,优惠/风控规则可替换
医院接口接入适配器 + 工厂 + 策略适配不同协议,按医院选择实现
数据导入模板方法 + 责任链模板固定流程,责任链做校验
权限和事务代理调用前后统一增强
本地扩展点观察者主流程发布事件,多个监听器扩展
Java IO装饰器一层层叠加缓冲、字符转换等能力

医疗数据采集平台组合示例

mermaid
flowchart TD
    A["采集任务"] --> B["医院适配器工厂"]
    B --> C["医院 A 适配器"]
    B --> D["医院 B 适配器"]
    C --> E["统一数据模型"]
    D --> E
    E --> F["数据校验责任链"]
    F --> G["清洗策略"]
    G --> H["入库"]
    H --> I["发布本地事件"]
    I --> J["审计监听器"]
    I --> K["同步 ES 监听器"]

这里不是为了堆模式,而是每个模式对应一个真实变化点:

  1. 医院协议会变,用适配器。
  2. 医院选择会变,用工厂。
  3. 校验步骤会增加,用责任链。
  4. 清洗规则会变化,用策略。
  5. 入库后扩展动作会增加,用观察者或 MQ。

阶段七:生产排查

策略选错

mermaid
flowchart TD
    A["策略结果异常"] --> B["检查 strategyKey"]
    B --> C["检查工厂注册表"]
    C --> D{"策略是否存在"}
    D -- "否" --> E["注册缺失或 key 不一致"]
    D -- "是" --> F["检查具体策略日志"]
    F --> G["检查入参和业务规则"]

需要记录:

字段作用
strategyKey本次选择依据
strategyClass实际执行类
requestId关联业务请求
costMs执行耗时
resultCode策略执行结果

责任链卡住或结果不对

mermaid
flowchart TD
    A["责任链异常"] --> B["检查处理器顺序"]
    B --> C["检查是否中断"]
    C --> D["检查异常是否被吞"]
    D --> E["检查每个处理器日志"]
    E --> F["定位具体处理器"]

代理失效

mermaid
flowchart TD
    A["代理增强未生效"] --> B["是否通过代理对象调用"]
    B --> C["是否同类自调用"]
    C --> D["方法是否 public"]
    D --> E["Bean 是否由 Spring 管理"]
    E --> F["切点表达式是否匹配"]

Spring 事务、缓存、异步、权限 AOP 失效时,经常是没有经过代理。

观察者事件丢失

mermaid
flowchart TD
    A["事件未触发"] --> B["是否发布事件"]
    B --> C["监听器是否注册为 Bean"]
    C --> D["事件类型是否匹配"]
    D --> E["是否异步线程异常"]
    E --> F["是否事务提交后才发布"]

如果事件必须可靠送达,考虑事务消息、本地消息表或 MQ,而不是普通本地观察者。

常见坑

后果正确做法
为了模式而模式类暴增,阅读困难先确认变化点
工厂里写业务流程工厂职责膨胀工厂只做对象选择
策略选择散落各处新增策略仍要多处改集中注册和选择
责任链无顺序管理执行结果不可控明确顺序和中断策略
代理自调用AOP 不生效通过代理对象调用或调整设计
适配器写复杂业务适配层变业务层只做协议和字段转换
观察者替代 MQ跨服务消息不可靠跨进程使用 MQ
Builder 不校验构造出非法对象build 阶段做校验

面试标准回答

设计模式怎么理解

text
设计模式是常见设计问题的经验总结,核心是识别变化点并隔离变化。它不是为了套 UML 或让代码复杂,而是在变化真实存在、扩展会持续发生时,让新增功能少改旧代码,也让每个变化点更容易测试和维护。同时要避免过度设计,简单稳定的业务直接写清楚更好。

工厂和策略怎么配合

text
工厂负责根据业务类型找到具体实现对象,策略负责执行具体业务规则。比如支付系统中,PayFactory 根据支付渠道找到支付宝、微信或银联支付策略,订单主流程只依赖 PayService 接口。新增渠道时主要新增策略实现,尽量不修改订单主流程。

代理和装饰器区别

text
代理和装饰器结构上都可能包一层对象,但意图不同。代理强调访问控制或横切增强,比如 Spring 事务、权限、远程代理;装饰器强调动态叠加能力,比如 Java IO 中给输入流增加缓冲、字符转换或压缩能力。

策略和模板方法区别

text
策略模式强调算法或规则可替换,调用方运行时选择不同策略;模板方法强调流程骨架稳定,把某些步骤交给子类实现。策略更偏组合,模板方法更偏继承。比如不同通知渠道适合策略,固定导入流程但解析步骤不同适合模板方法。

责任链适合什么场景

text
责任链适合多个处理器按顺序处理同一个请求,每个处理器职责单一,可以继续传递或中断。典型场景有订单创建校验、Servlet Filter、Gateway Filter、Spring Security 过滤器链。它的代价是链路变长后排查困难,所以必须有顺序管理、异常策略和链路日志。

学懂验收问题

下面问题答不上来,说明还没有真正学懂:

  1. 设计模式解决的是代码复用,还是变化隔离?
  2. 什么时候不应该用设计模式?
  3. Spring Bean 单例和手写单例有什么区别?
  4. 工厂模式隔离的是 new,还是实现选择和创建规则?
  5. Builder 为什么不等于 Lombok 注解?
  6. Spring 事务为什么是代理?为什么自调用会失效?
  7. 代理和装饰器为什么结构像但意图不同?
  8. 适配器里为什么不建议写复杂业务?
  9. 策略模式是不是为了消灭所有 if?
  10. 观察者和 MQ 的边界是什么?
  11. 模板方法和策略模式怎么选?
  12. 责任链为什么必须有顺序和日志?
  13. 支付系统为什么常用工厂 + 策略?
  14. 医院接口接入为什么适合适配器?
  15. 线上策略选错时怎么排查?

关联知识点跳转

主题继续学习
设计模式总览设计模式
生产路线设计模式从零到生产级掌握
商业场景设计模式商业场景训练营
单例单例模式
工厂工厂模式
建造者建造者模式
代理代理模式
装饰器装饰器模式
适配器适配器模式
策略策略模式
观察者观察者模式
模板方法模板方法模式
责任链责任链模式
Spring 事务代理Spring 事务
消息队列消息队列
面试设计模式面试题