设计模式从零到精通验收清单
设计模式不能学成“背名字、背 UML、背标准定义”。真正能在商业项目里用好设计模式,靠的是判断力:先看变化点,再看不抽象会怎样,最后判断某个模式是否真的能减少修改面、降低风险、提高可测试性。
本页按“是什么、为什么、怎么工作、不这样会怎样、商业场景、代码 Demo、常见坑、排查、面试”的标准,把创建型、结构型、行为型模式串成一套可验收的课程清单。
最终学习目标
学完设计模式模块,至少要能做到:
| 能力 | 合格标准 | 不合格表现 |
|---|---|---|
| 识别变化点 | 能说清哪里会变、哪里稳定 | 上来就问“用哪个模式” |
| 判断是否需要模式 | 能解释不用模式会怎样,用了有什么代价 | 简单逻辑也抽一堆接口 |
| 创建型模式 | 能区分单例、工厂、建造者 | 把工厂写成业务大杂烩 |
| 结构型模式 | 能区分代理、装饰器、适配器 | 只知道“都是包一层” |
| 行为型模式 | 能区分策略、模板方法、观察者、责任链 | 用策略消灭所有 if |
| Spring/JDK 应用 | 能指出 Spring AOP、Bean 工厂、事件、Java IO 背后的模式 | 只会背业务 Demo |
| 商业落地 | 能在支付、订单、导入、医院接口、权限、消息通知中选型 | 模式和场景对不上 |
| 生产排查 | 能排查策略选错、责任链卡住、代理失效、事件丢失 | 模式一多就看不懂链路 |
| 面试表达 | 能先讲业务问题,再讲模式和边界 | 只背定义,不讲为什么 |
总学习路线
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 种模式,而是先问四个问题:
- 这个业务哪里会变化?
- 这种变化会不会持续发生?
- 如果不抽象,新增需求要改多少旧代码?
- 如果抽象,会不会让代码更难读、更难排查?
阶段一:设计模式到底解决什么
是什么
设计模式是对常见设计问题的经验总结。它的核心不是“让代码高级”,而是隔离变化点,让稳定部分少改,让变化部分可扩展。
为什么需要
没有设计意识时,代码常见演化过程是:
flowchart TD
A["第一个需求:直接写 if"] --> B["第二个需求:再加 else"]
B --> C["第三个需求:复制一份改一改"]
C --> D["第四个需求:多人同时改同一大方法"]
D --> E["线上问题:漏改、错改、无法测试"]设计模式试图把变化隔离出来:
flowchart TD
A["稳定主流程"] --> B["依赖抽象接口"]
B --> C["变化实现 A"]
B --> D["变化实现 B"]
B --> E["变化实现 C"]
F["新增需求"] --> G["新增实现类"]
G --> B不这样会怎样
| 问题 | 后果 |
|---|---|
| 大量 if/else | 新增规则容易影响旧规则 |
| 业务层直接 new 具体实现 | 替换实现困难,单测困难 |
| 横切逻辑散落 | 日志、权限、事务重复且不一致 |
| 流程和步骤混在一起 | 主流程越来越长 |
| 多供应商接口直接写在业务中 | 每接一个供应商都改核心代码 |
阶段二:什么时候不要用设计模式
设计模式不是越多越好。
| 场景 | 建议 |
|---|---|
| 只有一个实现,短期不会扩展 | 先直接写清楚 |
| 业务规则还在频繁试错 | 先保持简单,等稳定后再抽象 |
| 抽象后类暴增但修改面没减少 | 不值得 |
| 新人读代码需要跳十几层 | 可能过度设计 |
| 模式只是为了“看起来专业” | 不要用 |
一个实用判断:
flowchart TD
A["看到重复或 if"] --> B{"变化是否真实存在"}
B -- "否" --> C["保持简单"]
B -- "是" --> D{"变化是否会持续发生"}
D -- "否" --> C
D -- "是" --> E{"抽象是否减少修改面"}
E -- "否" --> C
E -- "是" --> F["选择合适模式"]阶段三:创建型模式
创建型模式解决“对象怎么创建、谁来创建、创建过程如何统一”的问题。
单例模式
单例保证某个类在一个范围内只有一个实例。常见范围包括 JVM 级单例和 Spring 容器级单例。
flowchart TD
A["调用方"] --> B["getInstance 或 Spring 容器"]
B --> C{"实例是否存在"}
C -- "否" --> D["创建实例"]
C -- "是" --> E["返回已有实例"]
D --> E适合:
- 配置管理器。
- 线程池管理器。
- 连接池。
- Spring 默认单例 Bean。
不适合:
- 保存用户请求状态。
- 保存可变业务上下文。
- 需要多租户隔离的数据。
错误示例:
public class BadUserContext {
private static final BadUserContext INSTANCE = new BadUserContext();
private Long currentUserId;
public static BadUserContext getInstance() {
return INSTANCE;
}
}currentUserId 是请求级数据,放进全局单例会导致多线程串数据。
工厂模式
工厂封装“选择哪个实现”和“如何获取对象”。
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 --> GSpring Bean Map 工厂 Demo:
public interface PayService {
String channel();
void pay(PayRequest request);
}@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。
建造者模式
建造者解决“复杂对象参数多、可选字段多、默认值多、校验多”的问题。
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 逻辑里。
阶段四:结构型模式
结构型模式解决“对象如何组合、增强、兼容”的问题。
代理模式
代理强调访问控制或横切增强。调用方不直接访问目标对象,而是先经过代理。
flowchart TD
A["调用方"] --> B["代理对象"]
B --> C["权限、事务、日志、缓存"]
C --> D["目标对象"]
D --> E["返回结果"]
E --> BSpring 事务就是典型代理:
- 外部调用进入代理对象。
- 代理开启事务。
- 调用目标方法。
- 成功提交,异常回滚。
同类自调用事务失效,是因为没有经过代理对象。
装饰器模式
装饰器强调“动态叠加能力”,典型例子是 Java IO。
flowchart TD
A["FileInputStream"] --> B["BufferedInputStream"]
B --> C["DataInputStream"]
C --> D["调用方读取增强后的能力"]代理和装饰器结构都可能包一层,但意图不同:
| 对比 | 代理 | 装饰器 |
|---|---|---|
| 关注点 | 控制访问、横切增强 | 叠加能力 |
| 调用方是否关心增强能力 | 通常不关心 | 通常关心组合后的能力 |
| 典型场景 | Spring AOP、远程代理 | Java IO、响应包装 |
适配器模式
适配器解决“已有接口不兼容,但能力想复用”的问题。
flowchart TD
A["统一 HospitalClient"] --> B["医院 A 适配器"]
A --> C["医院 B 适配器"]
A --> D["医院 C 适配器"]
B --> E["HTTP + XML"]
C --> F["HTTP + JSON"]
D --> G["WebService"]医疗接口适配器 Demo:
public interface HospitalClient {
PatientReport queryReport(String patientId);
}@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()
);
}
}适配器主要做协议、字段、错误码转换,不要把复杂业务规则塞进去。
阶段五:行为型模式
行为型模式解决“多个对象之间如何协作、流程如何拆分、规则如何扩展”的问题。
策略模式
策略解决“同一件事有多种做法,并且做法会独立变化”。
flowchart TD
A["业务请求"] --> B["提取 strategyKey"]
B --> C["策略工厂"]
C --> D{"找到策略?"}
D -- "否" --> E["抛异常或默认策略"]
D -- "是" --> F["执行具体策略"]通知策略 Demo:
public interface NotifyStrategy {
String type();
void send(NotifyMessage message);
}@Component
public class SmsNotifyStrategy implements NotifyStrategy {
@Override
public String type() {
return "SMS";
}
@Override
public void send(NotifyMessage message) {
// 调短信服务
}
}策略不是为了消灭所有 if。少量稳定判断直接写清楚即可。
观察者模式
观察者解决“一件事发生后,通知多个本地扩展点”。
flowchart TD
A["订单支付成功"] --> B["发布本地事件"]
B --> C["发优惠券监听器"]
B --> D["写审计日志监听器"]
B --> E["站内信监听器"]本地事件适合进程内扩展。跨服务可靠通知不要用普通观察者替代 MQ,因为 MQ 提供持久化、重试、消费确认、堆积处理。
Spring 事件 Demo:
public record OrderPaidEvent(Long orderId, Long userId) {
}@Component
public class CouponListener {
@EventListener
public void onOrderPaid(OrderPaidEvent event) {
// 发放优惠券
}
}如果监听器失败是否影响主流程,要在业务上明确。关键通知一般不要只靠同步本地事件。
模板方法模式
模板方法解决“流程顺序稳定,但某些步骤变化”。
flowchart TD
A["模板方法 execute"] --> B["读取文件"]
B --> C["解析数据"]
C --> D["校验数据"]
D --> E["保存数据"]
E --> F["生成结果"]数据导入模板 Demo:
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);
}模板方法通常依赖继承。继承关系不自然时,优先考虑组合、策略或责任链。
责任链模式
责任链解决“多个处理器按顺序处理同一个请求”。
flowchart TD
A["订单创建请求"] --> B["参数校验"]
B --> C["库存校验"]
C --> D["价格校验"]
D --> E["风控校验"]
E --> F["创建订单"]订单校验链 Demo:
public interface OrderCheckHandler {
void check(OrderCreateContext context);
}@Component
@Order(10)
public class StockCheckHandler implements OrderCheckHandler {
@Override
public void check(OrderCreateContext context) {
if (context.getStock() <= 0) {
throw new IllegalStateException("库存不足");
}
}
}@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 | 装饰器 | 一层层叠加缓冲、字符转换等能力 |
医疗数据采集平台组合示例
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 监听器"]这里不是为了堆模式,而是每个模式对应一个真实变化点:
- 医院协议会变,用适配器。
- 医院选择会变,用工厂。
- 校验步骤会增加,用责任链。
- 清洗规则会变化,用策略。
- 入库后扩展动作会增加,用观察者或 MQ。
阶段七:生产排查
策略选错
flowchart TD
A["策略结果异常"] --> B["检查 strategyKey"]
B --> C["检查工厂注册表"]
C --> D{"策略是否存在"}
D -- "否" --> E["注册缺失或 key 不一致"]
D -- "是" --> F["检查具体策略日志"]
F --> G["检查入参和业务规则"]需要记录:
| 字段 | 作用 |
|---|---|
| strategyKey | 本次选择依据 |
| strategyClass | 实际执行类 |
| requestId | 关联业务请求 |
| costMs | 执行耗时 |
| resultCode | 策略执行结果 |
责任链卡住或结果不对
flowchart TD
A["责任链异常"] --> B["检查处理器顺序"]
B --> C["检查是否中断"]
C --> D["检查异常是否被吞"]
D --> E["检查每个处理器日志"]
E --> F["定位具体处理器"]代理失效
flowchart TD
A["代理增强未生效"] --> B["是否通过代理对象调用"]
B --> C["是否同类自调用"]
C --> D["方法是否 public"]
D --> E["Bean 是否由 Spring 管理"]
E --> F["切点表达式是否匹配"]Spring 事务、缓存、异步、权限 AOP 失效时,经常是没有经过代理。
观察者事件丢失
flowchart TD
A["事件未触发"] --> B["是否发布事件"]
B --> C["监听器是否注册为 Bean"]
C --> D["事件类型是否匹配"]
D --> E["是否异步线程异常"]
E --> F["是否事务提交后才发布"]如果事件必须可靠送达,考虑事务消息、本地消息表或 MQ,而不是普通本地观察者。
常见坑
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 为了模式而模式 | 类暴增,阅读困难 | 先确认变化点 |
| 工厂里写业务流程 | 工厂职责膨胀 | 工厂只做对象选择 |
| 策略选择散落各处 | 新增策略仍要多处改 | 集中注册和选择 |
| 责任链无顺序管理 | 执行结果不可控 | 明确顺序和中断策略 |
| 代理自调用 | AOP 不生效 | 通过代理对象调用或调整设计 |
| 适配器写复杂业务 | 适配层变业务层 | 只做协议和字段转换 |
| 观察者替代 MQ | 跨服务消息不可靠 | 跨进程使用 MQ |
| Builder 不校验 | 构造出非法对象 | build 阶段做校验 |
面试标准回答
设计模式怎么理解
设计模式是常见设计问题的经验总结,核心是识别变化点并隔离变化。它不是为了套 UML 或让代码复杂,而是在变化真实存在、扩展会持续发生时,让新增功能少改旧代码,也让每个变化点更容易测试和维护。同时要避免过度设计,简单稳定的业务直接写清楚更好。工厂和策略怎么配合
工厂负责根据业务类型找到具体实现对象,策略负责执行具体业务规则。比如支付系统中,PayFactory 根据支付渠道找到支付宝、微信或银联支付策略,订单主流程只依赖 PayService 接口。新增渠道时主要新增策略实现,尽量不修改订单主流程。代理和装饰器区别
代理和装饰器结构上都可能包一层对象,但意图不同。代理强调访问控制或横切增强,比如 Spring 事务、权限、远程代理;装饰器强调动态叠加能力,比如 Java IO 中给输入流增加缓冲、字符转换或压缩能力。策略和模板方法区别
策略模式强调算法或规则可替换,调用方运行时选择不同策略;模板方法强调流程骨架稳定,把某些步骤交给子类实现。策略更偏组合,模板方法更偏继承。比如不同通知渠道适合策略,固定导入流程但解析步骤不同适合模板方法。责任链适合什么场景
责任链适合多个处理器按顺序处理同一个请求,每个处理器职责单一,可以继续传递或中断。典型场景有订单创建校验、Servlet Filter、Gateway Filter、Spring Security 过滤器链。它的代价是链路变长后排查困难,所以必须有顺序管理、异常策略和链路日志。学懂验收问题
下面问题答不上来,说明还没有真正学懂:
- 设计模式解决的是代码复用,还是变化隔离?
- 什么时候不应该用设计模式?
- Spring Bean 单例和手写单例有什么区别?
- 工厂模式隔离的是
new,还是实现选择和创建规则? - Builder 为什么不等于 Lombok 注解?
- Spring 事务为什么是代理?为什么自调用会失效?
- 代理和装饰器为什么结构像但意图不同?
- 适配器里为什么不建议写复杂业务?
- 策略模式是不是为了消灭所有 if?
- 观察者和 MQ 的边界是什么?
- 模板方法和策略模式怎么选?
- 责任链为什么必须有顺序和日志?
- 支付系统为什么常用工厂 + 策略?
- 医院接口接入为什么适合适配器?
- 线上策略选错时怎么排查?
关联知识点跳转
| 主题 | 继续学习 |
|---|---|
| 设计模式总览 | 设计模式 |
| 生产路线 | 设计模式从零到生产级掌握 |
| 商业场景 | 设计模式商业场景训练营 |
| 单例 | 单例模式 |
| 工厂 | 工厂模式 |
| 建造者 | 建造者模式 |
| 代理 | 代理模式 |
| 装饰器 | 装饰器模式 |
| 适配器 | 适配器模式 |
| 策略 | 策略模式 |
| 观察者 | 观察者模式 |
| 模板方法 | 模板方法模式 |
| 责任链 | 责任链模式 |
| Spring 事务代理 | Spring 事务 |
| 消息队列 | 消息队列 |
| 面试 | 设计模式面试题 |
