JavaScript 设计模式(23 种)


文章目录

    • 设计模式简介
    • 设计模式原则(SOLID)
    • 一、创建型模式
      • [1. 工厂模式](#1. 工厂模式)
      • [2. 单例模式](#2. 单例模式)
      • [3. 原型模式](#3. 原型模式)
      • [4. 建造者模式](#4. 建造者模式)
      • [5. 抽象工厂模式](#5. 抽象工厂模式)
    • 二、结构型模式
      • [6. 适配器模式](#6. 适配器模式)
      • [7. 装饰器模式](#7. 装饰器模式)
      • [8. 代理模式](#8. 代理模式)
      • [9. 外观模式](#9. 外观模式)
      • [10. 桥接模式](#10. 桥接模式)
      • [11. 组合模式](#11. 组合模式)
      • [12. 享元模式](#12. 享元模式)
    • 三、行为型模式
      • [13. 观察者模式(发布---订阅)](#13. 观察者模式(发布—订阅))
      • [14. 策略模式](#14. 策略模式)
      • [15. 模板方法模式](#15. 模板方法模式)
      • [16. 职责链模式](#16. 职责链模式)
      • [17. 命令模式](#17. 命令模式)
      • [18. 状态模式](#18. 状态模式)
      • [19. 迭代器模式](#19. 迭代器模式)
      • [20. 备忘录模式](#20. 备忘录模式)
      • [21. 中介者模式](#21. 中介者模式)
      • [22. 访问者模式](#22. 访问者模式)
      • [23. 解释器模式](#23. 解释器模式)
    • 模式对比速查表

设计模式简介

设计模式是软件开发中反复出现的问题的通用解决方案,是经过大量实践验证的最佳实践总结。使用设计模式的核心价值:

  • 重用代码 --- 避免重复造轮子
  • 易于理解 --- 用公认的术语描述架构意图
  • 保证可靠性 --- 经过验证的成熟方案

设计模式原则(SOLID)

原则 说明 白话理解
S 单一职责 一个程序只做好一件事 功能复杂了就拆分
O 开放封闭 对扩展开放,对修改封闭 加功能加新代码,别改老代码
L 里氏替换 子类能覆盖父类 父类能出现的地方子类就能出现
I 接口隔离 保持接口的单一独立 别让一个接口干所有事
D 依赖倒转 面向接口编程 依赖抽象,不依赖具体实现

举个栗子 --- Promise 就体现了 S 和 O:

  • 单一职责 :每个 .then() 只做一件事
  • 开放封闭 :新增需求只需追加 .then(),无需改动已有链

再来一个反例 → 正例的演化:

js 复制代码
// ❌ 反例:每次加校验规则都要改函数内部 --- 违反开放封闭原则
const checkType = (str, type) => {
  switch (type) {
    case 'email':    return /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str)
    case 'mobile':   return /^1[3|4|5|7|8][0-9]{9}$/.test(str)
    case 'tel':      return /^(0\d{2,3}-\d{7,8})(-\d{1,4})?$/.test(str)
    default:         return true
  }
}

// ✅ 正例:开放扩展,封闭修改
const createValidator = () => {
  const rules = {
    email:  str => /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(str),
    mobile: str => /^1[3|4|5|7|8][0-9]{9}$/.test(str),
  }

  return {
    check: (str, type) => (rules[type] ? rules[type](str) : false),
    addRule: (type, fn) => { rules[type] = fn },
  }
}

const validator = createValidator()
validator.addRule('money', str => /^[0-9]+(\.[0-9]{2})?$/.test(str))

console.log(validator.check('18817023900', 'mobile')) // true
console.log(validator.check('18.36', 'money'))         // true

一、创建型模式

1. 工厂模式

定义一个创建对象的接口,由子类决定实例化哪个类,将 new 操作封装起来。

核心思想:把"创建什么对象"的决策从调用方移走,调用方只关心拿到的对象能干什么。

js 复制代码
// 产品:用户
const createUser = (name, role) => {
  const say = () => console.log(`我是${role}:${name}`)

  return role === 'admin'
    ? { name, role, say, manage: () => console.log('管理系统权限') }
    : { name, role, say }
}

// 工厂
const userFactory = {
  create: (name, role) => createUser(name, role),
}

// 使用
const admin = userFactory.create('小明', 'admin')
const guest = userFactory.create('小红', 'guest')

admin.say()   // 我是admin:小明
guest.say()   // 我是guest:小红

现实中的例子:

js 复制代码
// jQuery 的 $() 就是一个工厂 --- 根据传入参数不同创建不同的 jQuery 对象
const $ = (selector) => {
  const el = typeof selector === 'string'
    ? document.querySelectorAll(selector)
    : selector
  return {
    el,
    addClass: (cls) => { /* ... */ },
    on: (event, fn) => { /* ... */ },
  }
}
优点 缺点
创建过程复杂时,调用方只需关心结果 简单场景用工厂反而多此一举
构造函数和创建者分离,符合开闭原则 增加产品时要加新的工厂函数
扩展性好 引入抽象层,增加理解成本

适用场景:需要根据环境创建不同实例,且这些实例有相同行为时。

什么时候不用 :如果只是简单的 new,直接用构造函数更清晰。


2. 单例模式

一个类只有一个实例,并提供全局访问点。

js 复制代码
// 弹窗管理器 --- 整个应用只需要一个
const createModal = () => {
  let modalEl = null

  const show = (content) => {
    if (modalEl) {
      modalEl.style.display = 'block'
      return
    }
    modalEl = document.createElement('div')
    modalEl.className = 'modal'
    modalEl.textContent = content
    document.body.appendChild(modalEl)
  }

  const hide = () => {
    if (modalEl) modalEl.style.display = 'none'
  }

  return { show, hide }
}

// 利用闭包 + IIFE 保证单例
const getModal = (() => {
  let instance = null
  return () => {
    if (!instance) instance = createModal()
    return instance
  }
})()

// 使用
const m1 = getModal()
const m2 = getModal()
console.log(m1 === m2) // true --- 全局唯一

现实中的例子:

js 复制代码
// Vuex Store --- 一个应用只有一个 store
// Redux createStore --- 单例 store
// 全局缓存、全局配置对象
const globalConfig = (() => {
  const config = {}
  return {
    get: (key) => config[key],
    set: (key, val) => { config[key] = val },
  }
})()
优点 缺点
减少全局变量,增强模块性 强耦合,不利于单元测试
只实例化一次,节省内存 隐含的全局状态,调试困难

适用场景:登录弹窗、全局 store、数据库连接池、配置文件管理器。


3. 原型模式

用原型实例指定创建对象的种类,通过拷贝原型来创建新对象。

js 复制代码
// 汽车原型
const carPrototype = {
  wheels: 4,
  drive() { console.log(`${this.brand} 正在行驶...`) },
}

// 基于原型创建新对象
const createCar = (brand) => {
  const car = Object.create(carPrototype)
  car.brand = brand
  return car
}

const tesla = createCar('Tesla')
const bmw = createCar('BMW')

tesla.drive() // Tesla 正在行驶...
bmw.drive()   // BMW 正在行驶...
console.log(tesla.wheels) // 4(来自原型)

现实中的例子:

js 复制代码
// Object.create() 就是原型模式的直接应用
// JS 中 extends 本质上也是原型继承

// 配置模板 --- 创建多个带默认值的配置对象
const defaultConfig = { timeout: 3000, retry: 3, cache: true }
const apiConfig = { ...defaultConfig, url: '/api/users' }  // 展开运算符也是拷贝原型的思想
优点 缺点
通过拷贝创建重复对象,提升性能 深拷贝嵌套对象可能复杂
简化新对象创建

适用场景:需要大量创建相似对象,且创建成本较高时。


4. 建造者模式

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

核心思想:分步骤构建一个复杂对象,每一步只关注自己负责的部分,最终拼装出完整对象。

js 复制代码
// 建造一个汉堡套餐
const createMealBuilder = () => {
  const meal = {}

  const builder = {
    setMain: (main) => { meal.main = main; return builder },
    setDrink: (drink) => { meal.drink = drink; return builder },
    setSnack: (snack) => { meal.snack = snack; return builder },
    build: () => ({ ...meal, describe: () =>
      console.log(`套餐:${meal.main} + ${meal.drink} + ${meal.snack}`)
    }),
  }
  return builder
}

// 使用
const mealA = createMealBuilder()
  .setMain('汉堡')
  .setDrink('可乐')
  .setSnack('薯条')
  .build()

mealA.describe() // 套餐:汉堡 + 可乐 + 薯条

现实中的例子:

js 复制代码
// 表单构建器
const createFormBuilder = () => {
  const fields = []
  const builder = {
    addInput: (label, name) => {
      fields.push({ type: 'input', label, name })
      return builder
    },
    addSelect: (label, name, options) => {
      fields.push({ type: 'select', label, name, options })
      return builder
    },
    build: () => ({ fields, render: () => console.log('渲染表单:', fields) }),
  }
  return builder
}
优点 缺点
构建过程清晰可控,链式调用语义好 代码量增加
构建与表示分离,可创建不同表示 不适合简单对象(杀鸡用牛刀)

适用场景:需要构造包含多个可选参数的对象、构建步骤固定的复杂对象。


5. 抽象工厂模式

提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

和普通工厂的区别 :普通工厂生产一种产品,抽象工厂生产一组相关产品(产品族)。

js 复制代码
// 抽象工厂:UI 组件工厂 --- 为不同平台(Web / Mobile)创建一整套 UI 组件
const createWebUI = () => ({
  button: (text) => ({ render: () => `<button>${text}</button>` }),
  input: (placeholder) => ({ render: () => `<input placeholder="${placeholder}">` }),
})

const createMobileUI = () => ({
  button: (text) => ({ render: () => `[MobileBtn ${text}]` }),
  input: (placeholder) => ({ render: () => `[MobileInput hint="${placeholder}"]` }),
})

// 使用
const uiFactory = (platform) => platform === 'web' ? createWebUI() : createMobileUI()

const ui = uiFactory('web')
console.log(ui.button('提交').render()) // <button>提交</button>

现实中的例子:

js 复制代码
// 不同数据库驱动返回一整套操作(connect, query, close)
const createMySQLDriver = () => ({
  connect: () => console.log('MySQL 连接'),
  query: (sql) => console.log(`MySQL 执行:${sql}`),
})

const createPgDriver = () => ({
  connect: () => console.log('PostgreSQL 连接'),
  query: (sql) => console.log(`Pg 执行:${sql}`),
})
优点 缺点
保证产品族内对象的一致性 增加新产品族结构时需要改动工厂接口
客户端与具体产品解耦 结构较复杂

二、结构型模式

6. 适配器模式

将一个接口转换成客户期望的另一个接口,使原本不兼容的接口可以一起工作。

核心思想:像电源转换插头一样,在中间做一层"翻译"。

js 复制代码
// 新接口
const newRequest = ({ url, method = 'GET', data = {} }) =>
  fetch(url, { method, body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } })
    .then(res => res.json())

// 旧代码全用 $.ajax 的格式,做个适配器包一层
const $ = {
  ajax: ({ url, type = 'GET', dataType = 'json', data = {} }) =>
    newRequest({ url, method: type, data })
      .then(res => ({
        data: res,
        status: 200,
      })),
}

// 老代码不用改,直接用
$.ajax({ url: '/api/user', type: 'GET' }).then(({ data }) => console.log(data))

现实中的例子:

js 复制代码
// Vue computed --- 把 data 适配成模板需要的格式
const vm = {
  data: { firstName: 'Zhang', lastName: 'San' },
  computed: {
    fullName: ({ firstName, lastName }) => `${firstName} ${lastName}`, // 适配!
  },
}

// 把第三方 SDK 的数据格式转成自己项目的格式
const adaptUserData = (thirdPartyUser) => ({
  id: thirdPartyUser.user_id,
  name: thirdPartyUser.nickname,
  avatar: thirdPartyUser.head_img_url,
})
优点 缺点
让不兼容的接口可以协作 增加了一层调用
提高了类的复用度 如果可以直接改原接口,就别用适配器

🆚 适配器 vs 代理 :适配器提供不同的接口 ,代理提供一模一样的接口


7. 装饰器模式

不改变原对象,通过包装来动态添加功能。

核心思想:像给手机套壳 --- 手机本身没变,但多了保护功能。

js 复制代码
// 原函数:提交订单
const submitOrder = (order) => {
  console.log(`订单 ${order.id} 提交成功`)
}

// 装饰器:加上日志记录
const withLogging = (fn) => (...args) => {
  console.log(`[${new Date().toISOString()}] 开始执行`)
  const result = fn(...args)
  console.log(`[${new Date().toISOString()}] 执行完成`)
  return result
}

// 装饰器:加上性能监控
const withTimer = (fn) => (...args) => {
  const start = Date.now()
  const result = fn(...args)
  console.log(`耗时:${Date.now() - start}ms`)
  return result
}

// 叠加装饰
const enhancedSubmit = withTimer(withLogging(submitOrder))
enhancedSubmit({ id: 'A001' })

现实中的例子:

js 复制代码
// React HOC(高阶组件)
const withAuth = (Component) => (props) => {
  if (!props.isLogin) return <LoginPage />
  return <Component {...props} />
}

// Express/Koa 中间件 --- 在请求前后添加行为
const logger = (handler) => async (req, res) => {
  console.log(`${req.method} ${req.url}`)
  return handler(req, res)
}
优点 缺点
动态扩展功能,比继承更灵活 多层装饰可能让调用链复杂
装饰者和被装饰者解耦 产生很多小对象

8. 代理模式

为对象提供一个替身,以控制对这个对象的访问。

核心思想:你想追一个女孩,不直接送花,而是通过她的闺蜜在最合适的时机转交。

js 复制代码
// 原始图片加载
const loadImage = (src) => {
  const img = new Image()
  img.src = src
  return img
}

// 代理:加占位图和懒加载
const createImageProxy = (src, placeholder = '/loading.gif') => {
  const img = new Image()
  img.src = placeholder  // 先显示占位图

  const realImg = new Image()
  realImg.onload = () => { img.src = src }  // 真实图片加载完再替换
  realImg.src = src

  return { img, getSrc: () => img.src }
}

现实中的例子:

js 复制代码
// ES6 Proxy
const api = { getUser: (id) => fetch(`/api/user/${id}`) }
const apiWithCache = new Proxy(api, {
  cache: {},
  get(target, key) {
    const fn = target[key]
    return async (...args) => {
      const cacheKey = `${key}_${JSON.stringify(args)}`
      if (this.cache[cacheKey]) return this.cache[cacheKey]
      const result = await fn(...args)
      this.cache[cacheKey] = result
      return result
    }
  },
})

// DOM 事件代理
document.getElementById('list').addEventListener('click', (e) => {
  if (e.target.tagName === 'LI') console.log('点击了:', e.target.textContent)
})
优点 缺点
保护目标对象,控制访问 多一层间接调用,可能有性能差异
可扩展功能,符合开闭原则

🆚 代理 vs 装饰器 :代理是控制访问 (加限制),装饰器是扩展功能(不改变原有使用方式)。


9. 外观模式

为子系统的一组接口提供统一的高层接口,让子系统更容易使用。

核心思想:给你的复杂系统提供"一键操作"按钮。

js 复制代码
// 电脑开机 --- 内部很复杂,对外只需要一个按钮
const cpu = { start: () => console.log('CPU 启动') }
const memory = { load: () => console.log('内存加载') }
const disk = { read: () => console.log('硬盘读取') }

// 外观 --- 一键开机
const powerButton = {
  press: () => {
    cpu.start()
    memory.load()
    disk.read()
    console.log('✅ 开机完成')
  },
}

powerButton.press()

现实中的例子:

js 复制代码
// 兼容不同浏览器的事件绑定
const on = (el, event, handler) => {
  if (el.addEventListener) return el.addEventListener(event, handler, false)
  if (el.attachEvent) return el.attachEvent(`on${event}`, handler)
  el[`on${event}`] = handler
}

// 统一封装的 fetch 工具
const http = {
  get: (url, params)  => fetch(`${url}?${new URLSearchParams(params)}`).then(r => r.json()),
  post: (url, data)   => fetch(url, { method: 'POST', body: JSON.stringify(data) }).then(r => r.json()),
  put: (url, data)    => fetch(url, { method: 'PUT', body: JSON.stringify(data) }).then(r => r.json()),
  delete: (url)       => fetch(url, { method: 'DELETE' }).then(r => r.json()),
}
优点 缺点
减少系统间依赖,提供简洁入口 不符合开闭原则,外观改变可能影响多处
提高安全性和易用性

10. 桥接模式

将抽象与实现分离,使它们可以独立变化。

核心思想:让"做什么"和"怎么做"各自独立,通过组合关联。

js 复制代码
// "怎么做" --- 颜色实现
const colors = {
  red:   () => ({ name: '红色', hex: '#ff0000' }),
  blue:  () => ({ name: '蓝色', hex: '#0000ff' }),
  green: () => ({ name: '绿色', hex: '#00ff00' }),
}

// "做什么" --- 形状抽象
const createShape = (type, color) => ({
  draw: () => console.log(`绘制一个${color.name}(${color.hex})的${type}`),
})

// 组合使用
const red = colors.red()
const circle = createShape('圆形', red)
circle.draw() // 绘制一个红色(#ff0000)的圆形

现实中的例子:

js 复制代码
// 渲染器 + 数据源 的桥接
// 图表库:renderer 可以在 Canvas/SVG 之间切换,dataProvider 可以在 REST/WebSocket 之间切换
const createChart = ({ renderer, dataProvider }) => ({
  render: async () => {
    const data = await dataProvider.fetch()
    renderer.draw(data)
  },
})
优点 缺点
抽象和实现可独立扩展 类/函数数量增加
提高可扩展性

11. 组合模式

将对象组合成树形结构,以表示"整体---部分"的层次关系,让客户端对单个对象和组合对象的使用保持一致。

js 复制代码
// 文件系统 --- 文件和文件夹都能"显示大小"
const createFile = (name, size) => ({
  name,
  getSize: () => {
    console.log(`${name}: ${size}KB`)
    return size
  },
})

const createFolder = (name) => {
  const children = []
  return {
    name,
    add: (item) => { children.push(item) },
    getSize: () => {
      const total = children.reduce((sum, c) => sum + c.getSize(), 0)
      console.log(`${name} 文件夹总计: ${total}KB`)
      return total
    },
  }
}

// 使用
const home = createFolder('home')
home.add(createFile('readme.md', 2))
home.add(createFile('package.json', 5))

const src = createFolder('src')
src.add(createFile('index.js', 20))
src.add(createFile('utils.js', 10))
home.add(src)

home.getSize()
// readme.md: 2KB
// package.json: 5KB
// index.js: 20KB
// utils.js: 10KB
// src 文件夹总计: 30KB
// home 文件夹总计: 37KB

现实中的例子:

js 复制代码
// React 组件树 ------ 每个组件都是"可渲染"的,无论是叶子组件还是组合组件
const Button = (props) => render('button', props)
const Form = (props) => (
  render('form', null,
    props.children.map(child => child()) // 统一处理!
  )
)
优点 缺点
客户端统一处理单个对象和组合对象 对象太多时系统可能负担较重
易于扩展,新增类型无需改动使用方

12. 享元模式

运用共享技术有效支持大量细粒度对象的复用,减少内存占用。

核心思想 :把大量相似对象的共享状态 (内部状态)抽出来共用,非共享状态(外部状态)在使用时传入。

js 复制代码
// 驾考车 --- 只创建有限的几辆车,多考生排队复用
const createExamCarPool = (manualCount, autoCount) => {
  const cars = []
  let id = 0

  // 创建车辆(享元)
  const createCar = (type) => {
    const carId = ++id
    return {
      carId,
      type,
      examine: (candidateId) => new Promise((resolve) => {
        console.log(`考生-${candidateId} 在${type}车-${carId} 上考试`)
        setTimeout(() => {
          console.log(`考生-${candidateId} 考试完毕`)
          resolve()
        }, Math.random() * 2000 + 500)
      }),
    }
  }

  // 初始化车辆池
  for (let i = 0; i < manualCount; i++) cars.push(createCar('手动档'))
  for (let i = 0; i < autoCount; i++) cars.push(createCar('自动档'))

  // 分配车辆:找空闲车
  const queue = []
  const assign = (candidateId) => {
    const car = cars.find(c => !c.busy)
    if (car) {
      car.busy = true
      car.examine(candidateId).then(() => {
        car.busy = false
        if (queue.length) assign(queue.shift())  // 考完立即分配给下一个人
      })
    } else {
      queue.push(candidateId)
    }
  }

  return { assign, queue }
}

// 3 辆车,10 个考生
const pool = createExamCarPool(2, 1)
for (let i = 1; i <= 10; i++) pool.assign(i)
优点 缺点
大幅减少对象数量,节省内存 需要区分内部/外部状态,增加复杂度
外部状态管理不当容易混乱

适用场景:大量相似对象导致内存紧张时(如在线表格的单元格、游戏中的粒子、文件上传列表)。


三、行为型模式

13. 观察者模式(发布---订阅)

定义一对多的依赖关系,当一个对象状态变化时,所有依赖者自动收到通知。

js 复制代码
// 股票行情 --- 一个价格源,多个观察者
const createStock = (symbol, initPrice) => {
  const observers = new Set()
  let price = initPrice

  return {
    subscribe: (fn) => {
      observers.add(fn)
      return () => observers.delete(fn)  // 返回取消订阅函数
    },
    getPrice: () => price,
    setPrice: (newPrice) => {
      if (newPrice !== price) {
        price = newPrice
        observers.forEach(fn => fn(price)) // 通知所有观察者
      }
    },
  }
}

// 使用
const appleStock = createStock('AAPL', 150)

const unsubscribe = appleStock.subscribe((price) => {
  console.log(`📊 收到更新:AAPL 现价 $${price}`)
})

appleStock.setPrice(152)  // 📊 收到更新:AAPL 现价 $152
unsubscribe()             // 取消订阅
appleStock.setPrice(155)  // 无输出

现实中的例子:

js 复制代码
// DOM 事件
document.addEventListener('click', () => console.log('clicked'))

// Vue 响应式 / EventEmitter / RxJS
// Redux store.subscribe()
const store = {
  listeners: [],
  subscribe: (fn) => { store.listeners.push(fn); return () => store.listeners = store.listeners.filter(l => l !== fn) },
  dispatch: (action) => store.listeners.forEach(fn => fn(action)),
}
优点 缺点
广播通信,对象间解耦 过度使用会让调用链路难以追踪
灵活,可动态增减观察者 通知顺序可能不可控

14. 策略模式

定义一系列算法,封装起来使它们可以互相替换。算法的变化不会影响使用算法的客户端。

js 复制代码
// 电商折扣计算 --- 不同折扣策略
const discountStrategies = {
  normal:    (price) => price,
  vip:       (price) => price * 0.9,
  superVip:  (price) => price * 0.8,
  seasonal:  (price) => price * 0.75,
}

const calcPrice = (price, strategy) =>
  (discountStrategies[strategy] || discountStrategies.normal)(price)

// 使用
console.log(calcPrice(100, 'vip'))       // 90
console.log(calcPrice(100, 'seasonal'))  // 75

现实中的例子 --- 表单校验:

js 复制代码
const validateStrategies = {
  required: (val) => val.trim() ? '' : '必填',
  minLen: (len) => (val) => val.length >= len ? '' : `至少${len}个字符`,
  isEmail: (val) => /^[\w-]+@[\w-]+\.[\w-]+$/.test(val) ? '' : '邮箱格式不正确',
  isMobile: (val) => /^1[3-9]\d{9}$/.test(val) ? '' : '手机号格式不正确',
}

// 校验工厂
const createValidator = (rules) => (value) => {
  for (const [strategy, param] of rules) {
    const fn = typeof param === 'function' ? param : validateStrategies[strategy](param)
    const error = fn(value)
    if (error) return error
  }
  return ''
}

// 使用
const validateName = createValidator([
  ['required'],
  ['minLen', 2],
])
console.log(validateName(''))   // 必填
console.log(validateName('张')) // 至少2个字符
优点 缺点
避免多重 if-else / switch 策略类/函数增多
符合开闭原则,算法可灵活替换 使用者需要了解各策略的区别

15. 模板方法模式

在一个方法中定义算法的骨架,将一些步骤延迟到子函数中实现。

js 复制代码
// 饮品制作模板 --- 步骤固定,具体实现可变
const makeDrink = ({ brew, addFlavor }) => () => {
  // 这些步骤是固定的
  const boilWater = () => console.log('① 烧开水')
  const pourIntoCup = () => console.log('③ 倒进杯子')

  boilWater()
  brew()         // 可变步骤 1
  pourIntoCup()
  addFlavor()    // 可变步骤 2
  console.log('✅ 制作完成\n')
}

// 咖啡
const makeCoffee = makeDrink({
  brew:      () => console.log('② 冲泡咖啡粉'),
  addFlavor: () => console.log('④ 加牛奶和糖'),
})

// 茶
const makeTea = makeDrink({
  brew:      () => console.log('② 浸泡茶叶'),
  addFlavor: () => console.log('④ 加柠檬'),
})

makeCoffee()
makeTea()

现实中的例子:

js 复制代码
// Vue/React 生命周期钩子 ------ 框架定义了渲染流程(模板方法),
// 你在 mounted/componentDidMount 里填充自己的逻辑
// Axios 拦截器也是模板方法的体现
优点 缺点
公共代码集中维护,避免重复 步骤固定,灵活性受限于模板定义
子函数只需关心自己那部分

16. 职责链模式

将请求沿链传递,直到有对象处理它为止。

js 复制代码
// 请假审批链:组长(≤1天) → 经理(≤3天) → 总监(>3天)
const createApprover = (name, limit, next = null) => ({
  handle: (days) => {
    if (days <= limit) {
      console.log(`${name} 批准了 ${days} 天假期`)
      return true
    }
    if (next) return next.handle(days)
    console.log(`无人能批准 ${days} 天假期`)
    return false
  },
})

const director = createApprover('总监', 10)
const manager  = createApprover('经理', 3, director)
const leader   = createApprover('组长', 1, manager)

leader.handle(1)  // 组长 批准了 1 天假期
leader.handle(2)  // 经理 批准了 2 天假期
leader.handle(5)  // 总监 批准了 5 天假期

现实中的例子:

js 复制代码
// DOM 事件冒泡 --- 点击 li,事件沿 li → ul → div → body 传递
// Koa/Express 中间件 --- 请求依次经过各中间件,某个中间件处理后就返回
// JS 原型链 --- 属性查找沿 __proto__ 向上
优点 缺点
请求发送者和接收者解耦 不能保证请求一定被处理
可动态调整链的顺序和成员 长链可能带来性能问题

17. 命令模式

将请求封装成对象,使你可以用不同的请求参数化客户端,支持排队、日志、撤销。

js 复制代码
// 遥控器 --- 每个按钮是一个命令
const createTV = () => {
  let on = false
  let volume = 10

  return {
    turnOn: () => { on = true; console.log('📺 电视开了') },
    turnOff: () => { on = false; console.log('📺 电视关了') },
    volumeUp: () => { volume++; console.log(`🔊 音量:${volume}`) },
    volumeDown: () => { volume = Math.max(0, volume - 1); console.log(`🔉 音量:${volume}`) },
  }
}

// 命令调度器 --- 支持撤销
const createRemote = (tv) => {
  const history = []

  const execute = (command) => {
    const [action, undo] = command
    action()
    history.push({ action, undo })
  }

  const undo = () => {
    if (!history.length) return console.log('没有可撤销的操作')
    const cmd = history.pop()
    cmd.undo()
  }

  return { execute, undo }
}

// 定义命令(执行 + 撤销 配对)
const commands = (tv) => ({
  on:   [() => tv.turnOn(),  () => tv.turnOff()],
  off:  [() => tv.turnOff(), () => tv.turnOn()],
  up:   [() => tv.volumeUp(), () => tv.volumeDown()],
  down: [() => tv.volumeDown(), () => tv.volumeUp()],
})

const tv = createTV()
const remote = createRemote(tv)
const cmd = commands(tv)

remote.execute(cmd.on)   // 📺 电视开了
remote.execute(cmd.up)   // 🔊 音量:11
remote.execute(cmd.up)   // 🔊 音量:12
remote.undo()            // 🔉 音量:11
remote.undo()            // 🔊 音量:10
优点 缺点
命令和执行者解耦 命令类/对象增多
支持撤销、重做、命令队列

适用场景:编辑器撤销/重做、任务队列、宏命令录制。


18. 状态模式

对象内部状态改变时,其行为也随之改变。看起来就像换了类一样。

js 复制代码
// 交通灯 --- 状态切换逻辑
const createTrafficLight = () => {
  const states = {
    red: {
      color: '🔴',
      duration: 3000,
      next: () => 'green',
    },
    green: {
      color: '🟢',
      duration: 3000,
      next: () => 'yellow',
    },
    yellow: {
      color: '🟡',
      duration: 1000,
      next: () => 'red',
    },
  }

  let current = 'red'
  let timer = null

  const run = () => {
    const state = states[current]
    console.log(`${state.color} ${current.toUpperCase()} --- 持续${state.duration / 1000}秒`)
    timer = setTimeout(() => {
      current = state.next()
      run()
    }, state.duration)
  }

  const stop = () => clearTimeout(timer)

  return { run, stop, getState: () => current }
}

// 使用:
// const light = createTrafficLight()
// light.run()  // 🔴 → 🟢 → 🟡 → 🔴 ...

现实中的例子:

js 复制代码
// Promise 的 pending → fulfilled / rejected 状态转换
// 订单状态:待付款 → 已付款 → 已发货 → 已收货 → 已完成
const createOrder = () => {
  const states = {
    pending:   { pay: () => 'paid', cancel: () => 'cancelled' },
    paid:      { ship: () => 'shipped', refund: () => 'refunding' },
    shipped:   { receive: () => 'completed' },
    completed: {},
    cancelled: {},
  }

  let state = 'pending'
  return {
    getState: () => state,
    transition: (action) => {
      const next = states[state]?.[action]
      if (!next) return console.log(`❌ 当前[${state}]不能执行[${action}]`)
      state = next
      console.log(`✅ ${action} → ${state}`)
    },
  }
}
优点 缺点
消除大量 if-else,状态逻辑清晰 状态类变多
状态切换一目了然

19. 迭代器模式

提供一种顺序访问集合元素的方法,而不暴露集合的内部结构。

js 复制代码
// 自定义迭代器
const createIterator = (arr) => {
  let index = 0
  return {
    next: () => (index < arr.length
      ? { value: arr[index++], done: false }
      : { value: undefined, done: true }
    ),
  }
}

const it = createIterator([10, 20, 30])
let result = it.next()
while (!result.done) {
  console.log(result.value) // 10, 20, 30
  result = it.next()
}

现实中的例子:

js 复制代码
// ES6 内置:Array.prototype[Symbol.iterator]
// for...of 就是基于迭代器协议
// Generator 函数
const range = function* (start, end) {
  for (let i = start; i <= end; i++) yield i
}
for (const n of range(1, 5)) console.log(n) // 1, 2, 3, 4, 5
优点 缺点
统一遍历接口,不暴露内部结构 简单遍历使用 forEach 更直接
支持不同数据结构用同一套算法

20. 备忘录模式

在不破坏封装的前提下,捕获对象内部状态并在外部保存,以便后续恢复。

js 复制代码
// 文本编辑器 --- 支持撤销
const createEditor = () => {
  let content = ''
  const history = []  // 备忘录栈
  let cursor = -1

  const save = () => {
    // 保存当前状态
    history.length = cursor + 1  // 清除"未来"历史
    history.push(content)
    cursor++
  }

  return {
    type: (text) => {
      save()
      content += text
      console.log(`📝 当前内容: "${content}"`)
    },
    undo: () => {
      if (cursor <= 0) return console.log('无法撤销')
      cursor--
      content = history[cursor]
      console.log(`↩ 撤销后: "${content}"`)
    },
    redo: () => {
      if (cursor >= history.length - 1) return console.log('无法重做')
      cursor++
      content = history[cursor]
      console.log(`↪ 重做后: "${content}"`)
    },
    getContent: () => content,
  }
}

const editor = createEditor()
editor.type('Hello')
editor.type(' World')
editor.undo()       // "Hello"
editor.undo()       // ""
editor.redo()       // "Hello"
优点 缺点
提供状态恢复机制 保存大量状态会占用内存
不破坏封装性

适用场景:编辑器撤销/重做、游戏存档、表单草稿保存。


21. 中介者模式

用一个中介对象封装一系列对象之间的交互,使各对象不需要显式地相互引用。

js 复制代码
// 聊天室 --- 中介者转发消息,用户之间不直接通信
const createChatRoom = () => {
  const users = {}

  return {
    register: (user) => { users[user.name] = user; user.setRoom(this) },
    send: (from, to, msg) => {
      if (to) {
        // 私聊
        users[to]?.receive(from, msg)
      } else {
        // 广播
        Object.keys(users).forEach(name => {
          if (name !== from) users[name].receive(from, msg)
        })
      }
    },
  }
}

const createUser = (name) => {
  let room = null
  return {
    name,
    setRoom: (r) => { room = r },
    send: (to, msg) => room.send(name, to, msg),
    receive: (from, msg) => console.log(`[${name}] 收到 ${from}: ${msg}`),
  }
}

const room = createChatRoom()
const alice = createUser('Alice')
const bob = createUser('Bob')
const carol = createUser('Carol')

room.register(alice)
room.register(bob)
room.register(carol)

alice.send('', '大家好!')  // 广播
bob.send('Alice', '你好 Alice')  // 私聊
优点 缺点
网状多对多 → 一对多,降低耦合 中介者本身可能变得臃肿
各对象独立,易于复用

22. 访问者模式

在不改变元素类的前提下,定义作用于元素的新操作。

js 复制代码
// 对不同的员工类型执行不同的操作(计算薪资、生成报表)
const employeeTypes = {
  engineer: (name, salary) => ({
    type: 'engineer',
    name,
    salary,
    accept: (visitor) => visitor.visitEngineer({ name, salary }),
  }),
  manager: (name, salary, bonus) => ({
    type: 'manager',
    name,
    salary,
    bonus,
    accept: (visitor) => visitor.visitManager({ name, salary, bonus }),
  }),
}

// 访问者:计算年终奖
const bonusCalculator = {
  visitEngineer: (e) => e.salary * 2,
  visitManager: (m) => m.salary * 3 + m.bonus,
}

// 访问者:生成工资条
const payslipGenerator = {
  visitEngineer: (e) => `${e.name}(工程师): 月薪 ${e.salary}`,
  visitManager: (m) => `${m.name}(经理): 月薪 ${m.salary} + 奖金 ${m.bonus}`,
}

// 使用
const alice = employeeTypes.engineer('Alice', 15000)
const bob = employeeTypes.manager('Bob', 20000, 5000)

console.log(alice.accept(bonusCalculator))   // 30000
console.log(bob.accept(bonusCalculator))     // 65000
console.log(alice.accept(payslipGenerator))  // Alice(工程师): 月薪 15000
优点 缺点
数据结构和操作分离,扩展操作方便 元素需要暴露内部信息给访问者
符合单一职责 新增元素类型时需要修改所有访问者

23. 解释器模式

定义一种语言的文法表示,并提供一个解释器来解释这种语言中的句子。

js 复制代码
// 简易计算器解释器 --- 解析 "数字 操作符 数字"
const tokenize = (input) => {
  const tokens = []
  const regex = /\d+|[+\-*/]/g
  let match
  while ((match = regex.exec(input)) !== null) tokens.push(match[0])
  return tokens
}

const interpret = (tokens) => {
  // 数字解析
  const number = (token) => {
    const num = parseInt(token, 10)
    if (isNaN(num)) throw new Error(`"${token}" 不是有效数字`)
    return num
  }

  // 操作符解析
  const operators = {
    '+': (a, b) => a + b,
    '-': (a, b) => a - b,
    '*': (a, b) => a * b,
    '/': (a, b) => {
      if (b === 0) throw new Error('除数不能为 0')
      return a / b
    },
  }

  const operator = (token) => {
    const fn = operators[token]
    if (!fn) throw new Error(`未知运算符: ${token}`)
    return fn
  }

  // 执行:左 → 右,不考虑优先级,以演示为目的
  let result = number(tokens[0])
  for (let i = 1; i < tokens.length; i += 2) {
    const op = operator(tokens[i])
    const num = number(tokens[i + 1])
    result = op(result, num)
  }
  return result
}

// 使用
const calculate = (expr) => interpret(tokenize(expr))
console.log(calculate('3 + 5 * 2')) // 16(先算 3+5=8,再算 8*2=16,无优先级)
console.log(calculate('10 / 2 + 3')) // 8
优点 缺点
易于扩展语法规则 复杂文法时性能较差
复杂文法难以维护,应考虑用现成的解析器

适用场景:SQL 解析、正则表达式、模板引擎、自定义 DSL。


模式对比速查表

对比项 模式 A 模式 B 核心区别
创建对象 工厂 建造者 工厂一步创建;建造者分步构建
创建对象 工厂 抽象工厂 工厂造一种产品;抽象工厂造一族产品
包装对象 适配器 代理 适配器改接口;代理不改接口
包装对象 代理 装饰器 代理控制访问;装饰器增强功能
解耦 观察者 中介者 观察者一对多广播;中介者一对多集中管理
解耦 策略 状态 策略外部选择算法;状态内部切换行为
结构 组合 享元 组合组织树形结构;享元共享实例减少内存

总结 :设计模式不是银弹,而是工具箱。关键是在合适场景选用合适的模式,不要为了用模式而用模式。理解每种模式要解决的问题,比记住它的写法更重要。

相关推荐
EntyIU1 小时前
Java NIO 实战
java·开发语言·nio
用户69371750013841 小时前
AI 领域的 Harness,到底是什么意思?
android·前端·后端
spider_xcxc1 小时前
Redis 深度实践:安全管控、性能压测与持久化分析(二)
运维·前端·redis·云计算·bootstrap·运维开发
乘风gg1 小时前
代码都已经让 AI 写了,为什么有些人却更慌了?
前端·ai编程·claude
我不是外星人1 小时前
一键还原任意网站,这套 Playwright + 多 Agent 工程流太猛了
前端·后端·ai编程
浩瀚之水_csdn2 小时前
Python 3 网络编程详解:从原理到实战
开发语言·网络·python
用户059540174462 小时前
AI 客服突然“失忆”,排查 6 小时发现 LangChain 记忆存储的 3 个致命坑
前端·css
geovindu2 小时前
java: Singleton Pattern
java·开发语言·后端·单例模式·设计模式·创建型模式
AI行业学习2 小时前
2026 版 Notepad++ 完整图文安装指南|官方渠道无捆绑,一键切换中文界面
开发语言·人工智能·python·html·notepad++