Skip to content

设计模式概述与分类 ​

设计模式不是「要背的 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 是什么模式装饰器
什么时候别用模式抽象带来的复杂度超过了它省下的修改成本