设计模式概述与分类
设计模式不是「要背的 23 个名字」,而是给常见问题起的名字。 它的真正价值:你遇到问题时能意识到「这是策略/职责链」,从而不必从零设计。
一、先看这张判断表
| 你的问题 | 该想的模式 |
|---|---|
| if-else / switch 分支越来越多 | 策略、表驱动、多态 |
| 一堆对象要互相通知 | 观察者 / 发布订阅 |
| 想给某段逻辑「加一层」而不改它 | 装饰器、代理、中间件(职责链) |
| 创建对象的过程很复杂(多参数、多步骤) | 工厂、建造者 |
| 同一个复杂接口天天用,但只用其中两点 | 外观(Facade) |
| 想撤销 / 重放操作 | 命令 + 备忘录 |
| 状态多、状态间跳转复杂 | 状态机 |
二、三大分类
| 分类 | 关注点 | 模式数 |
|---|---|---|
| 创建型 | 对象怎么创建 | 5 |
| 结构型 | 对象怎么组合 | 7 |
| 行为型 | 对象怎么通信 | 11 |
三、创建型模式
| 模式 | 作用 | 前端应用 |
|---|---|---|
| 单例 | 确保一个类只有一个实例 | Pinia/Vuex store、全局配置、弹窗管理器、Axios 实例 |
| 工厂 | 封装对象创建逻辑 | 按类型创建不同 UI 组件、请求实例 |
| 抽象工厂 | 创建相关对象族 | 主题系统(暗色/亮色整套组件) |
| 建造者 | 分步构建复杂对象 | 请求构建器、Webpack 配置链 |
| 原型 | 通过克隆创建对象 | Object.create、对象深拷贝 |
四、结构型模式
| 模式 | 作用 | 前端应用 |
|---|---|---|
| 代理 | 为对象提供替代或占位 | 虚拟滚动、图片懒加载、Vue3 响应式 |
| 装饰器 | 动态添加行为 | 高阶组件(HOC)、TS 装饰器、@sealed |
| 适配器 | 接口兼容转换 | 浏览器 API 兼容层、数据格式适配 |
| 组合 | 树形结构统一处理 | React/Vue 组件树、菜单递归渲染 |
| 外观 | 简化复杂系统接口 | 封装复杂 API、SDK |
| 桥接 | 抽象与实现分离 | 跨平台渲染(React Native 的 View) |
| 享元 | 共享细粒度对象 | 长列表重复元素、池化 |
五、行为型模式
| 模式 | 作用 | 前端应用 |
|---|---|---|
| 观察者 | 一对多依赖通知 | 事件系统、Vue2 的 Dep/Watcher |
| 发布订阅 | 解耦发布者与订阅者 | EventBus、跨组件通信 |
| 策略 | 算法族可互换 | 表单校验、多端支付、动画策略 |
| 状态 | 状态变更行为 | 状态机(XState)、UI 状态流转 |
| 职责链 | 链式处理请求 | Koa/Express 中间件、Vue Router 守卫、Webpack Loader |
| 模板方法 | 规定算法骨架 | 组件生命周期、构建流程 |
| 迭代器 | 顺序访问集合 | for...of、Generator |
| 命令 | 请求封装为对象 | 撤销/重做、操作日志 |
| 备忘录 | 状态保存与恢复 | 编辑器历史、表单草稿 |
| 中介者 | 对象间通信解耦 | 弹窗调度中心、事件总线 |
| 访问者 | 结构与操作分离 | AST 遍历(Babel 插件) |
六、三段真正会用到的代码
1. 策略:干掉 if-else
js
// ❌ 每加一种就改一次 if
function getPrice(type, amount) {
if (type === 'vip') return amount * 0.8
if (type === 'svip') return amount * 0.7
if (type === 'new') return amount - 10
}
// ✅ 策略表:新增类型只加一行,不改函数
const strategies = {
vip: (a) => a * 0.8,
svip: (a) => a * 0.7,
new: (a) => a - 10,
default: (a) => a,
}
const getPrice = (type, amount) => (strategies[type] ?? strategies.default)(amount)// ❌ 每加一种就改一次 if
function getPrice(type, amount) {
if (type === 'vip') return amount * 0.8
if (type === 'svip') return amount * 0.7
if (type === 'new') return amount - 10
}
// ✅ 策略表:新增类型只加一行,不改函数
const strategies = {
vip: (a) => a * 0.8,
svip: (a) => a * 0.7,
new: (a) => a - 10,
default: (a) => a,
}
const getPrice = (type, amount) => (strategies[type] ?? strategies.default)(amount)这就是「表驱动」 —— 前端里最常用、最容易被认可的模式。
2. 发布订阅:跨组件通信
js
const bus = new Map()
export const on = (event, fn) => {
if (!bus.has(event)) bus.set(event, new Set())
bus.get(event).add(fn)
return () => bus.get(event).delete(fn) // 返回取消函数,避免内存泄漏
}
export const emit = (event, payload) => {
bus.get(event)?.forEach(fn => fn(payload))
}const bus = new Map()
export const on = (event, fn) => {
if (!bus.has(event)) bus.set(event, new Set())
bus.get(event).add(fn)
return () => bus.get(event).delete(fn) // 返回取消函数,避免内存泄漏
}
export const emit = (event, payload) => {
bus.get(event)?.forEach(fn => fn(payload))
}注意:Vue3 移除了 $on/$off/$emit,就是因为事件总线难以追踪;跨层级优先用 provide/inject 或状态库。
3. 代理:图片懒加载
js
// 代理模式:真实操作被推迟到「需要时」
const imgProxy = new Proxy(img, {
set(target, key, value) {
if (key === 'src') {
// 先占位,进入视口再真正加载
observer.observe(target)
pending.set(target, value)
return true
}
return Reflect.set(target, key, value)
},
})// 代理模式:真实操作被推迟到「需要时」
const imgProxy = new Proxy(img, {
set(target, key, value) {
if (key === 'src') {
// 先占位,进入视口再真正加载
observer.observe(target)
pending.set(target, value)
return true
}
return Reflect.set(target, key, value)
},
})七、「你项目里早就用了」映射表
| 你天天用的东西 | 背后是哪个模式 |
|---|---|
Vue3 的 reactive | 代理 |
Vue2 的 Dep/Watcher | 观察者 |
| Redux 的 middleware | 职责链 |
Koa 的 app.use() | 职责链 |
Vue Router 的 beforeEach | 职责链 |
React 的 memo / HOC | 装饰器 |
element-ui 的按需加载 | 工厂 + 外观 |
| Pinia 的 store | 单例 |
| Babel 插件遍历 AST | 访问者 |
| 表单校验规则数组 | 策略 |
结论:模式不是额外发明的东西,是给已经在用的做法命名。真正要练的是「识别它、说清它解决什么问题」。
八、别滥用
| 过度设计的信号 | 后果 |
|---|---|
一个 if 能解决的事,上了策略 + 工厂 + 注册表 | 别人看不懂,改动要碰三个文件 |
| 为了「可扩展」提前抽象 | 需求没来,抽象先僵化 |
| 每一层都加「通用」封装 | 调用链变长、调试困难 |
判断标准:模式是为了降低修改成本。如果一个模式让你改一行要动五个文件,那它已经变成负债。
九、速查
| 问题 | 答案 |
|---|---|
| 三大分类 | 创建型(怎么造)、结构型(怎么组)、行为型(怎么通信) |
| 干掉 if-else 用什么 | 策略 + 表驱动 |
| 跨组件通信用什么 | 发布订阅(谨慎)/ provide-inject / 状态库 |
| Vue3 reactive 是什么模式 | 代理 |
| Koa 中间件是什么模式 | 职责链 |
| HOC 是什么模式 | 装饰器 |
| 什么时候别用模式 | 抽象带来的复杂度超过了它省下的修改成本 |
Cao Note