什么是设计模式?它能带来哪些好处?
结论先行:设计模式(Design Pattern)是对"某个场景下反复出现的设计问题"的成熟解法模板,不是可以直接 import 的代码。它真正解决的是两件事:把变化点从稳定结构里挤出去,以及让团队用同一个词指代同一个结构。
1. 拆开定义:三个要素
一个模式必须同时说清三件事,缺一个就退化成"代码片段":
| 要素 | 回答什么 | 反例(只有代码没有模式) |
|---|---|---|
| 场景(Context) | 在什么约束下用 | 看到 Map 就说是策略模式 |
| 问题(Problem) | 哪些力在互相拉扯 | 只说"为了解耦",不说解耦什么 |
| 方案(Solution) | 用什么结构平衡这些力 | 贴一段类图,讲不出权衡 |
面试里比较稳的说法是:「模式是在特定上下文中,对重复出现的设计问题的可复用解决思路,它描述的是对象之间的关系,而不是某段具体实现。」
2. 模式、算法、框架不是一回事
| 概念 | 回答的问题 | 形态 | 例子 |
|---|---|---|---|
| 算法(Algorithm) | 怎么把输入算成输出 | 步骤序列 | 快排、Dijkstra |
| 数据结构(Data Structure) | 数据怎么存 | 内存布局 | 红黑树、跳表 |
| 设计模式(Pattern) | 对象怎么组织、职责怎么分 | 类/对象关系 | 责任链、观察者 |
| 框架(Framework) | 骨架由谁定、谁被调用 | 可运行的半成品 | Spring、Netty |
| 架构风格(Style) | 系统怎么分层、怎么部署 | 拓扑与约束 | 分层、事件驱动、微服务 |
一句话记:算法管计算,数据结构管存储,模式管关系,框架管控制反转。
3. 好处一:把"会变的部分"圈出来
以订单下单为例,稳定的是主流程,易变的是各类规则:
┌──────────────────────────────┐
下单主流程 │ 校验 → 计价 → 锁库存 → 落单 → 通知 │ ← 半年不动
└──────────────────────────────┘
▲ ▲ ▲
│ │ │
┌────┴───┐ ┌────┴────┐ ┌───┴─────┐
│ 计价规则 │ │ 库存策略 │ │ 通知渠道 │ ← 每周都在加
└────────┘ └─────────┘ └─────────┘
把三个易变点抽成接口(策略 / 责任链 / 观察者),主流程只依赖接口。以后加一个"直播专享价",动的是插件,不是主流程——这就是**开闭原则(Open-Closed Principle)**在工程里的具体样子。
4. 好处二:模式名是团队的压缩词汇
| 你想表达的意思 | 用模式名一句话 | 省掉的解释 |
|---|---|---|
| 多层审批,每层都可能处理或放行 | "这里走责任链" | 不用画三层 if 的流程图 |
| 订单状态流转,每个状态行为不同 | "用状态模式" | 不用解释为什么不用 switch |
| 事件发生了,多个下游各自关心 | "发个观察者事件" | 不用逐个打电话通知 |
| 多种计费规则可切换 | "抽成策略" | 不用争论要不要写 if-else |
新人听到"责任链"就能立刻想到"链表 + 命中即停 + 可能穿透到底",沟通成本被压到接近零。这也是模式最被低估的收益。
5. 好处三:约束实现方式,减少踩坑
模式自带"标准骨架",照着写不容易漏掉关键点。比如建造者(Builder)解决了构造器参数爆炸:
public final class Order {
private final long userId;
private final String sku;
private final int qty;
private final String coupon;
private final boolean gift;
private Order(Builder b) {
this.userId = b.userId;
this.sku = b.sku;
this.qty = b.qty;
this.coupon = b.coupon;
this.gift = b.gift;
}
public static class Builder {
private final long userId;
private final String sku;
private int qty = 1; // 可选参数给默认值
private String coupon;
private boolean gift;
public Builder(long userId, String sku) { // 必填项进构造器
this.userId = userId;
this.sku = sku;
}
public Builder qty(int q) { this.qty = q; return this; }
public Builder coupon(String c) { this.coupon = c; return this; }
public Builder gift(boolean g) { this.gift = g; return this; }
public Order build() {
if (qty <= 0) {
throw new IllegalArgumentException("qty 必须大于 0");
}
return new Order(this);
}
}
}
// 调用:链式赋值,参数顺序不再靠记忆
Order o = new Order.Builder(1001L, "SKU-9527").qty(2).gift(true).build();
收益是具体的:必填/可选分离、校验集中在 build()、对象对外不可变。
6. 代价:模式不是越多越好
- 过度设计:只有三个分支就上"策略 + 工厂 + 注册表 + 配置中心",读代码要跳五个文件。
- 间接层成本:多一层接口就多一次跳转,栈更深、日志更难串、调试要打好几个断点。
- 黑话风险:团队对模式理解不一致时,"这里用桥接"反而制造误解。
判断标准很朴素:变化已经发生,或者有明确证据即将发生,才值得抽。只是"感觉以后可能会变",先写直白代码。
7. 23 种模式的分类
| 分类 | 关注点 | 包含模式 |
|---|---|---|
| 创建型(Creational) | 对象怎么造 | 单例、工厂方法、抽象工厂、建造者、原型 |
| 结构型(Structural) | 类与对象怎么组合 | 代理、适配器、桥接、组合、装饰器、外观、享元 |
| 行为型(Behavioral) | 职责怎么分派、怎么协作 | 责任链、命令、解释器、迭代器、中介者、备忘录、观察者、状态、策略、模板方法、访问者 |
8. 常见追问
Q:设计模式是银弹吗? 不是。它是对既有经验的归纳,不是发明。用错场景的模式比不用更糟——比如给只有一种实现的接口硬套抽象工厂。
Q:Java 新特性会不会让模式消失?
部分会被语言特性吸收:函数式接口和 lambda 让"策略/命令"可以只写一个方法引用;enum 让单例几乎零成本;record 让不可变值对象不用手写建造者。但结构性问题仍在——需要运行时替换实现、需要横切增强时,代理和装饰器依然要写。
Q:面试怎么答才不像背书? 给一个自己改过的场景:「报表导出原来是一坨 if-else,新增渠道要改主流程还容易漏,后来抽成策略 + 注册表,新增渠道只加一个类和一个配置项」,再补一句代价「类多了,但渠道确实每周都在加」。有前后对比、有权衡,就是真实经验。
小结
- 模式 = 场景 + 问题 + 方案,描述对象关系,不是代码模板。
- 三大收益:隔离变化、统一团队词汇、用标准骨架少踩坑。
- 三大代价:过度设计、间接层、黑话;是否抽象看"变化是否真的发生"。
- 23 种按创建、结构、行为三类记,答题时先讲分类再讲具体。