Chendi WuMediaBlog
Back to blog

设计模式

常见设计模式(创建型 / 结构型 / 行为型)的笔记与示例。

2026-05-16
NotesDesign Patterns

前言

概括

设计模式是软件开发中,针对特定问题的、可复用的、经过验证的优雅解决方案。

它们不是具体的代码或算法,而是一种编程思想和代码组织方式。

为什么需要设计模式?

  1. 可复用性:将成熟的设计方案应用到不同项目中,避免重复造轮子。
  2. 可读性与可维护性:使用统一的"设计语言",让团队成员能快速理解代码结构,降低维护成本。
  3. 可靠性:这些模式经过大量实践检验,使用它们可以构建更稳定、更健壮的系统。
  4. 提升沟通效率:当你说"这里用个单例模式"时,大家都能立刻明白你的意图。

设计模式的三大分类

🏗️ 创建型模式

核心思想:专注于"怎么优雅地创建对象",让对象创建更灵活可控,将创建与使用解耦。

模式目的关键词
工厂模式定义创建对象的接口,让子类决定实例化哪一个类。封装创建、解耦、产品族
单例模式保证一个类仅有一个实例,并提供一个全局访问点。唯一实例、全局访问
原型模式通过复制现有实例来创建新的实例,而非通过 new。克隆、复制、高效创建
建造者模式将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。链式调用、分步构建、复杂对象

🧱 结构型模式

核心思想:专注于"如何组合类和对象",通过巧妙的组合形成更大、更灵活的结构。

模式目的关键词
适配器模式将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。接口转换、兼容、包装器
装饰器模式动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。动态增强、包装、非继承
代理模式为其他对象提供一种代理以控制对这个对象的访问。访问控制、代理、中间人
外观模式为子系统中的一组接口提供一个一致的界面,定义一个高层接口,这个接口使得这一子系统更加容易使用。简化接口、封装子系统
桥接模式将抽象部分与它的实现部分分离,使它们都可以独立地变化。抽象与实现分离、多维变化
组合模式将对象组合成树形结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。树形结构、部分-整体、统一操作
享元模式运用共享技术来有效地支持大量细粒度的对象,避免大量相似对象的开销。共享、池化、减少内存占用

🚀 行为型模式

核心思想:专注于"对象之间如何协作和通信",定义对象间的职责分配和交互方式。

模式目的关键词
策略模式定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。算法封装、可替换、消灭 if-else
模板方法模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。算法骨架、子类实现细节、代码复用
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。一对多依赖、发布-通知、状态变化
发布订阅模式一种消息范式,发送者(发布者)不会将消息直接发送给特定的接收者(订阅者)。解耦、消息队列、事件总线
迭代器模式提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。顺序访问、统一遍历接口
职责链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。请求传递、链式处理、多级审批
命令模式将一个请求封装成一个对象,从而使你可用不同的请求对客户进行参数化。请求封装、撤销/重做、队列
备忘录模式在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。状态保存、快照、撤销
状态模式允许一个对象在其内部状态改变时改变它的行为。状态驱动行为、多状态对象
访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。操作分离、稳定数据结构、易变操作
中介者模式用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散。中心协调、解耦多对多关系
解释器模式给定一种语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。文法解析、DSL、较少使用

如何选择设计模式?

场景/问题推荐模式典型应用
需要根据不同条件创建不同对象,但不想让调用者与具体类耦合工厂模式创建不同类型的数据库连接、UI 组件
需要保证全局只有一个实例(如配置、日志、连接池)单例模式全局配置管理器、日志记录器
想在不修改原有对象的情况下,动态地增加或修改其功能装饰器模式给文本添加格式、给图片添加滤镜
想要控制对某个对象的访问(如延迟加载、权限控制)代理模式图片懒加载、API 权限控制
有两个不兼容的接口需要协同工作适配器模式整合第三方库、旧系统接口适配
系统非常复杂,需要提供一个简化的统一入口外观模式封装复杂的底层库、提供简化 API
有一组算法可以互相替换,并希望避免使用大量的 if-else策略模式不同的排序算法、支付方式选择
一个对象状态改变时,需要自动通知其他多个对象观察者模式数据绑定、事件监听系统
想将请求的发送者和接收者完全解耦发布订阅模式消息队列、事件总线
有一个固定的处理流程,但其中某些步骤需要由子类实现模板方法模式数据处理流程、测试框架
需要快速复制大量相似对象原型模式游戏角色克隆、文档模板
构建复杂对象时,希望通过链式调用分步设置属性建造者模式构建复杂配置对象、SQL 查询构建器
需要遍历集合,但不想暴露集合内部结构迭代器模式自定义集合遍历、树形结构遍历
请求需要经过多个处理者,每个处理者负责一部分逻辑职责链模式多级审批流程、中间件系统
需要将操作封装成对象,支持撤销/重做命令模式编辑器的撤销/重做、事务管理

⚠️ 使用设计模式的注意事项

  1. 不要过度设计:简单问题不需要复杂模式,不要为了用模式而用模式。
  2. 理解场景再使用:每个模式都有其适用场景,不是所有项目都需要所有模式。
  3. 团队共识很重要:确保团队成员理解你使用的模式,否则会增加维护难度。
  4. 性能 vs 可维护性:有些模式会引入额外的抽象层,需要权衡性能和代码可维护性。
  5. 从简单开始:先掌握常用的模式(单例、工厂、策略、观察者),再逐步学习其他模式。

创建型 (Creational)

单例模式

保证一个类只有一个实例,并提供全局访问点。常用于配置、日志、连接池、缓存等。

class Singleton {
  static #instance: Singleton | null = null
  name: string

  constructor(name: string) {
    if (Singleton.#instance) return Singleton.#instance
    this.name = name
    Singleton.#instance = this
  }
}

const a = new Singleton('first')
const b = new Singleton('second')

a === b // true
b.name  // 'first'

闭包变体(隐藏内部实现,调用方不感知 class):

const Singleton = (() => {
  class Real {
    constructor(public name: string) {}
  }
  let instance: Real | null = null
  return (name: string) => (instance ??= new Real(name))
})()

工厂模式

把对象的创建过程封装起来,调用方无需感知具体类。jQuery $()、React.createElement 都是典型的工厂。

class Product {
  constructor(public name: string) {}
  use(): void { console.log(`using ${this.name}`) }
}

class ProductFactory {
  create(name: string): Product {
    return new Product(name)
  }
}

const factory = new ProductFactory()
factory.create('widget').use() // using widget

$() 一类的"工厂入口"封装思想:

class Query {
  elements: Element[]
  constructor(selector: string) {
    this.elements = [...document.querySelectorAll(selector)]
  }
  addClass(name: string): this { /* ... */ return this }
}

// 不暴露 new,调用方只需要一个统一入口
const $ = (selector: string): Query => new Query(selector)

原型模式

通过复制现有实例来创建新实例,避免昂贵的 new / 初始化逻辑。JS 自带的 structuredClone() / Object.create() 就是原型模式的天然支持。

class Doc {
  title = ''
  tags: string[] = []
  author: { name: string } = { name: '' }

  clone(): Doc {
    return structuredClone(this)
  }
}

const template = new Doc()
template.title = 'Hello'
template.tags.push('draft')

const copy = template.clone()
copy.title = 'World'
copy.tags.push('done')

template.title // 'Hello'
template.tags  // ['draft']
copy.title     // 'World'
copy.tags      // ['draft', 'done']   ← 深拷贝,互不影响

建造者模式

把复杂对象的构建拆成若干步骤,链式调用更可读,每一步都能独立替换实现。SQL 构造器 / 配置对象构造器都是典型案例。

class QueryBuilder {
  private fields: string[] = ['*']
  private where: string[] = []
  private order: string | null = null
  private limit: number | null = null

  constructor(private table: string) {}

  select(...fields: string[]): this { this.fields = fields; return this }
  filter(cond: string): this        { this.where.push(cond); return this }
  orderBy(col: string): this        { this.order = col; return this }
  take(n: number): this             { this.limit = n; return this }

  build(): string {
    const parts = [`SELECT ${this.fields.join(', ')} FROM ${this.table}`]
    if (this.where.length) parts.push(`WHERE ${this.where.join(' AND ')}`)
    if (this.order)        parts.push(`ORDER BY ${this.order}`)
    if (this.limit)        parts.push(`LIMIT ${this.limit}`)
    return parts.join(' ')
  }
}

new QueryBuilder('users')
  .select('id', 'name')
  .filter('age > 18')
  .filter('active = true')
  .orderBy('id DESC')
  .take(10)
  .build()
// SELECT id, name FROM users WHERE age > 18 AND active = true ORDER BY id DESC LIMIT 10

结构型 (Structural)

代理模式

在目标对象前增加一层代理,控制访问、做权限校验或懒加载。代理与目标接口保持一致。

interface Image {
  show(): void
}

class RealImage implements Image {
  constructor(private file: string) {
    console.log(`load ${file}`)
  }
  show(): void { console.log(`show ${this.file}`) }
}

class ImageProxy implements Image {
  private real: RealImage | null = null
  constructor(private file: string) {}

  show(): void {
    this.real ??= new RealImage(this.file)
    this.real.show()
  }
}

const img: Image = new ImageProxy('pic.png')
img.show()
// load pic.png
// show pic.png

ES6 Proxy 是语言层面的代理(拦截读 / 写 / 删 / 调用):

interface Star {
  name: string
  phone: string
  price?: number
}

const star: Star = {
  name: 'Star',
  phone: '189****0511',
}

const agent = new Proxy(star, {
  get(target, key) {
    if (key === 'phone') return '经纪人:189****1234'
    return target[key as keyof Star]
  },
  set(target, key, value) {
    if (key === 'price' && value < 100000) throw new Error('低于底价')
    ;(target as Record<string | symbol, unknown>)[key] = value
    return true
  },
})

agent.phone // '经纪人:189****1234'

对比

  • 代理 vs 适配器:代理保持原接口;适配器改变接口。
  • 代理 vs 装饰器:装饰器扩展功能;代理限制 / 控制原功能。

外观模式

给一组复杂调用提供一个简化的统一接口。常用于封装第三方库 / 复杂子系统。

type EventHandler = (e: Event) => void

function bindEvent(
  elem: HTMLElement,
  type: string,
  selectorOrFn: string | EventHandler,
  fn?: EventHandler,
): void {
  let selector: string | null
  let handler: EventHandler
  if (typeof selectorOrFn === 'function') {
    selector = null
    handler = selectorOrFn
  } else {
    selector = selectorOrFn
    handler = fn!
  }
  elem.addEventListener(type, (e) => {
    if (!selector || (e.target as Element).matches(selector)) handler(e)
  })
}

// 同一个入口,参数自适应
declare const list: HTMLElement
declare const btn: HTMLElement
bindEvent(list, 'click', 'li', (e) => console.log((e.target as Element).textContent))
bindEvent(btn, 'click', () => console.log('clicked'))

缺点:违反单一职责,封装层多了之后内部分支会变复杂,不要滥用。

装饰器模式

在不修改原对象的前提下动态加功能。装饰器和被装饰对象互相独立,符合开放封闭原则。

interface Shape {
  draw(): void
}

class Circle implements Shape {
  draw(): void { console.log('画一个圆') }
}

class BorderDecorator implements Shape {
  constructor(private shape: Shape) {}
  draw(): void {
    this.shape.draw()
    console.log('加一圈红色边框')
  }
}

new BorderDecorator(new Circle()).draw()
// 画一个圆
// 加一圈红色边框

TypeScript 5 Stage-3 装饰器语法(类装饰器):

function tagged(tag: string) {
  return <T extends new (...args: any[]) => any>(
    _value: T,
    context: ClassDecoratorContext<T>,
  ) => {
    context.addInitializer(function (this: T) {
      ;(this as unknown as { tag: string }).tag = tag
    })
  }
}

@tagged('hero')
class Player {}

;(Player as unknown as { tag: string }).tag // 'hero'

方法装饰器(自动 log):

function log<This, Args extends unknown[], Return>(
  value: (this: This, ...args: Args) => Return,
  context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>,
) {
  return function (this: This, ...args: Args): Return {
    console.log(`call ${String(context.name)}`, args)
    const result = value.apply(this, args)
    console.log(`-> ${result}`)
    return result
  }
}

class Calculator {
  @log
  add(a: number, b: number): number { return a + b }
}

new Calculator().add(2, 4)
// call add [ 2, 4 ]
// -> 6

适配器模式

把一个接口转换成调用方期望的另一种接口。Vue 的 computed、新旧 API 兼容封装都是适配器思想。

class EuropePlug {
  europeOutput(): string { return '220V · 欧标' }
}

class ChinaAdapter {
  constructor(private plug: EuropePlug) {}
  chinaOutput(): string {
    return `${this.plug.europeOutput()} -> 转换 -> 220V · 国标`
  }
}

new ChinaAdapter(new EuropePlug()).chinaOutput()
// 220V · 欧标 -> 转换 -> 220V · 国标

封装旧接口让它符合调用方期望:

type AjaxOptions = { url: string; method?: string }
type AjaxResult = { done(cb: () => void): void }

declare function ajax(opts: AjaxOptions): AjaxResult

// 旧接口签名:ajax(opts).done(cb)
// 调用方期望:$.ajax(opts).done(cb)
const $ = {
  ajax: (options: AjaxOptions): AjaxResult => ajax(options),
}

桥接模式

把"抽象"和"实现"拆成两条独立维度,用组合替代继承,让两边都能独立演进。

// 实现维度:渲染器
interface Renderer {
  draw(shape: string): void
}

class CanvasRenderer implements Renderer {
  draw(shape: string): void { console.log(`canvas: ${shape}`) }
}

class SvgRenderer implements Renderer {
  draw(shape: string): void { console.log(`svg: ${shape}`) }
}

// 抽象维度:形状
abstract class ShapeBridge {
  constructor(protected renderer: Renderer) {}
  abstract render(): void
}

class CircleShape extends ShapeBridge {
  render(): void { this.renderer.draw('circle') }
}

class SquareShape extends ShapeBridge {
  render(): void { this.renderer.draw('square') }
}

new CircleShape(new CanvasRenderer()).render() // canvas: circle
new SquareShape(new SvgRenderer()).render()    // svg: square

不用桥接:N 种形状 × M 种渲染器 = N×M 个子类;用桥接:N + M 个类。

组合模式

用树形结构表达"部分 / 整体",让叶子节点和容器节点对外接口完全一致。

interface FsNode {
  totalSize(): number
  print(prefix?: string): void
}

class FileNode implements FsNode {
  constructor(private name: string, private size: number) {}
  totalSize(): number { return this.size }
  print(prefix = ''): void {
    console.log(`${prefix}- ${this.name} (${this.size}B)`)
  }
}

class FolderNode implements FsNode {
  private children: FsNode[] = []
  constructor(private name: string) {}
  add(node: FsNode): this { this.children.push(node); return this }
  totalSize(): number {
    return this.children.reduce((sum, c) => sum + c.totalSize(), 0)
  }
  print(prefix = ''): void {
    console.log(`${prefix}+ ${this.name}/`)
    this.children.forEach((c) => c.print(prefix + '  '))
  }
}

const root = new FolderNode('root')
  .add(new FileNode('readme.md', 200))
  .add(
    new FolderNode('src')
      .add(new FileNode('index.js', 500))
      .add(new FileNode('utils.js', 300)),
  )

root.totalSize() // 1000
root.print()
// + root/
//   - readme.md (200B)
//   + src/
//     - index.js (500B)
//     - utils.js (300B)

享元模式

把可共享的"内部状态"抽出来缓存复用,"外部状态"作为参数传入,避免重复创建大量相似对象。

// 内部状态:字形数据可复用
class Glyph {
  path: string
  constructor(public char: string) {
    this.path = `path-data-for-${char}` // 假装是几百 KB 的字形数据
  }
  drawAt(x: number, y: number): void {
    console.log(`draw "${this.char}" at (${x}, ${y})`)
  }
}

// 享元工厂:相同 char 复用一份 Glyph
const GlyphFactory = (() => {
  const cache = new Map<string, Glyph>()
  return {
    get(char: string): Glyph {
      if (!cache.has(char)) cache.set(char, new Glyph(char))
      return cache.get(char)!
    },
    size(): number { return cache.size },
  }
})()

const text = 'hello'
for (let i = 0; i < text.length; i++) {
  GlyphFactory.get(text[i]).drawAt(i * 10, 0)
}

GlyphFactory.size() // 4   ← h / e / l / o,'l' 复用了

行为型 (Behavioral)

策略模式

把一族算法封装成可互换的策略对象,按 key 取用,消除大量 if-else / switch。

type Strategy = (price: number) => number

const sale: Record<string, Strategy> = {
  '100-10': (price) => price - 10,
  '200-25': (price) => price - 25,
  '80%':    (price) => price * 0.8,
}

function calcPrice(price: number, type: string): number {
  const fn = sale[type]
  if (!fn) throw new Error(`不存在 ${type} 折扣`)
  return fn(price)
}

;(calcPrice as { add(type: string, fn: Strategy): void }).add = (type, fn) => {
  sale[type] = fn
}
;(calcPrice as { del(type: string): void }).del = (type) => {
  delete sale[type]
}

;(calcPrice as { add(type: string, fn: Strategy): void }).add(
  '70%',
  (price) => price * 0.7,
)
calcPrice(320, '70%') // 224

观察者模式

一对多依赖:被观察者状态变化时,自动通知所有观察者。观察者由 Subject 直接持有。

type ObserverFn<T> = (state: T) => void

class Observer<T> {
  constructor(public name: string, public fn: ObserverFn<T>) {}
}

class Subject<T> {
  private observers: Observer<T>[] = []

  constructor(private state: T) {}

  add(observer: Observer<T>): void {
    if (!this.observers.includes(observer)) this.observers.push(observer)
  }

  remove(observer: Observer<T>): void {
    this.observers = this.observers.filter((o) => o !== observer)
  }

  setState(state: T): void {
    this.state = state
    this.observers.forEach((o) => o.fn(state))
  }
}

const teacher = new Observer<string>('老师', (s) => console.log(`老师:${s}`))
const principal = new Observer<string>('校长', (s) => console.log(`校长:${s}`))

const student = new Subject<string>('上课睡觉')
student.add(teacher)
student.add(principal)
student.setState('打架')
// 老师:打架
// 校长:打架

发布订阅模式

在发布方与订阅方之间加一个事件总线。两端互不感知,只跟总线打交道。

type Listener = (...args: unknown[]) => void

class EventBus {
  private events: Record<string, Listener[]> = {}

  on(type: string, cb: Listener): void {
    ;(this.events[type] ??= []).push(cb)
  }

  off(type: string, cb?: Listener): void {
    if (!cb) { delete this.events[type]; return }
    if (!this.events[type]) return
    this.events[type] = this.events[type].filter((c) => c !== cb)
  }

  emit(type: string, ...args: unknown[]): void {
    this.events[type]?.forEach((cb) => cb(...args))
  }
}

const bus = new EventBus()
const onPing: Listener = (msg) => console.log(`ping: ${msg}`)

bus.on('ping', onPing)
bus.emit('ping', 'hello')   // ping: hello

bus.off('ping', onPing)
bus.emit('ping', 'world')   // 无输出

对比

  • 观察者:Subject 直接持有 Observer 列表,强耦合。
  • 发布订阅:通过事件总线解耦,发布者和订阅者互不知道彼此存在。

模板方法模式

父类定义算法骨架(固定顺序),把具体步骤留给子类实现。骨架统一,差异下沉。

abstract class DataPipeline<Raw, Cleaned> {
  run(): number {
    const raw = this.fetch()
    const cleaned = this.transform(raw)
    return this.save(cleaned)
  }
  protected abstract fetch(): Raw
  protected transform(x: Raw): Cleaned { return x as unknown as Cleaned } // 默认透传
  protected abstract save(x: Cleaned): number
}

class CsvPipeline extends DataPipeline<string, string[][]> {
  protected fetch(): string { return 'a,b,c\n1,2,3' }
  protected transform(s: string): string[][] {
    return s.split('\n').slice(1).map((r) => r.split(','))
  }
  protected save(rows: string[][]): number {
    console.log('saved rows:', rows)
    return rows.length
  }
}

new CsvPipeline().run()
// saved rows: [ [ '1', '2', '3' ] ]   -> 1

迭代器模式

提供统一的遍历接口,调用方无需关心集合内部结构。JS 用 [Symbol.iterator] 就能让对象支持 for..of / 展开。

class Range {
  constructor(
    private start: number,
    private end: number,
    private step = 1,
  ) {}

  *[Symbol.iterator](): IterableIterator<number> {
    for (let i = this.start; i < this.end; i += this.step) yield i
  }
}

const r = new Range(0, 10, 2)

for (const n of r) console.log(n)
// 0, 2, 4, 6, 8

console.log([...r]) // [0, 2, 4, 6, 8]

职责链模式

多个处理者串成一条链,请求沿链传递,由第一个能处理 / 拒绝的节点决定结果。常用于中间件、拦截器、多级审批。

type Request = { user: string | null; qps: number }
type Response = { code: number; msg?: string; data?: string }

abstract class Handler {
  private next: Handler | null = null
  setNext(next: Handler): Handler { this.next = next; return next }
  handle(req: Request): Response | null {
    return this.next ? this.next.handle(req) : null
  }
}

class AuthHandler extends Handler {
  handle(req: Request): Response | null {
    if (!req.user) return { code: 401, msg: 'unauthorized' }
    return super.handle(req)
  }
}

class RateLimitHandler extends Handler {
  handle(req: Request): Response | null {
    if (req.qps > 100) return { code: 429, msg: 'rate limited' }
    return super.handle(req)
  }
}

class BusinessHandler extends Handler {
  handle(req: Request): Response {
    return { code: 200, data: `hello ${req.user}` }
  }
}

const chain = new AuthHandler()
chain.setNext(new RateLimitHandler()).setNext(new BusinessHandler())

chain.handle({ user: 'alice', qps: 10 })   // { code: 200, data: 'hello alice' }
chain.handle({ user: null,    qps: 10 })   // { code: 401, msg: 'unauthorized' }
chain.handle({ user: 'bob',   qps: 999 })  // { code: 429, msg: 'rate limited' }

命令模式

把请求封装成对象(含执行 + 撤销逻辑),便于队列化、日志、撤销 / 重做。

interface Command {
  execute(): void
  undo(): void
}

class TextEditor {
  content = ''
  append(text: string): void { this.content += text }
  remove(len: number): void  { this.content = this.content.slice(0, -len) }
}

class AppendCommand implements Command {
  constructor(private editor: TextEditor, private text: string) {}
  execute(): void { this.editor.append(this.text) }
  undo(): void    { this.editor.remove(this.text.length) }
}

class CommandHistory {
  private stack: Command[] = []
  do(cmd: Command): void { cmd.execute(); this.stack.push(cmd) }
  undo(): void { this.stack.pop()?.undo() }
}

const editor = new TextEditor()
const history = new CommandHistory()

history.do(new AppendCommand(editor, 'hello '))
history.do(new AppendCommand(editor, 'world'))
editor.content // 'hello world'

history.undo()
editor.content // 'hello '

备忘录模式

在不破坏封装的前提下,把对象内部状态保存为"快照",便于恢复(撤销 / 时间旅行调试)。

interface EditorMemento {
  readonly text: string
}

class Editor {
  constructor(public text = '') {}

  // 创建快照:导出内部状态
  save(): EditorMemento { return { text: this.text } }

  // 恢复快照
  restore(memento: EditorMemento): void { this.text = memento.text }
}

class Caretaker {
  private snapshots: EditorMemento[] = []
  push(memento: EditorMemento): void { this.snapshots.push(memento) }
  pop(): EditorMemento | undefined { return this.snapshots.pop() }
}

const editor = new Editor()
const history = new Caretaker()

editor.text = 'hello'
history.push(editor.save())

editor.text = 'hello world'
history.push(editor.save())

editor.text = 'oops'
editor.restore(history.pop()!) // 回到 'hello world'
editor.text                    // 'hello world'

状态模式

把一个对象在不同状态下的行为,拆到各自的状态类里;切换状态等于切换行为,消除大块 switch。

interface JobState {
  next(ctx: Job): void
  describe(): string
}

class IdleState implements JobState {
  next(ctx: Job): void { ctx.setState(new WorkingState()) }
  describe(): string { return 'idle' }
}

class WorkingState implements JobState {
  next(ctx: Job): void { ctx.setState(new DoneState()) }
  describe(): string { return 'working...' }
}

class DoneState implements JobState {
  next(ctx: Job): void { ctx.setState(new IdleState()) }
  describe(): string { return 'done!' }
}

class Job {
  private state: JobState = new IdleState()
  setState(s: JobState): void { this.state = s }
  next(): void { this.state.next(this) }
  toString(): string { return this.state.describe() }
}

const job = new Job()
console.log(`${job}`) // idle
job.next(); console.log(`${job}`) // working...
job.next(); console.log(`${job}`) // done!
job.next(); console.log(`${job}`) // idle

访问者模式

把作用在对象结构上的"操作"抽到访问者里,新增操作不需要改动数据结构本身。

interface ShapeVisitor<R> {
  visitCircle(node: CircleNode): R
  visitSquare(node: SquareNode): R
}

interface ShapeNode {
  accept<R>(v: ShapeVisitor<R>): R
}

class CircleNode implements ShapeNode {
  constructor(public r: number) {}
  accept<R>(v: ShapeVisitor<R>): R { return v.visitCircle(this) }
}

class SquareNode implements ShapeNode {
  constructor(public side: number) {}
  accept<R>(v: ShapeVisitor<R>): R { return v.visitSquare(this) }
}

// 操作 1:算面积
class AreaVisitor implements ShapeVisitor<number> {
  visitCircle(n: CircleNode): number { return Math.PI * n.r ** 2 }
  visitSquare(n: SquareNode): number { return n.side ** 2 }
}

// 操作 2:生成 SVG
class SvgVisitor implements ShapeVisitor<string> {
  visitCircle(n: CircleNode): string { return `<circle r="${n.r}" />` }
  visitSquare(n: SquareNode): string {
    return `<rect width="${n.side}" height="${n.side}" />`
  }
}

const shapes: ShapeNode[] = [new CircleNode(3), new SquareNode(4)]
const area = new AreaVisitor()
const svg = new SvgVisitor()

shapes.map((s) => s.accept(area)) // [ 28.274..., 16 ]
shapes.map((s) => s.accept(svg))  // [ '<circle r="3" />', '<rect width="4" height="4" />' ]

中介者模式

多对多协作交给一个中介统一调度,参与者之间不直接持有彼此引用。聊天室、UI 控件协同都是典型例子。

class ChatRoom {
  private users: User[] = []
  join(user: User): void { this.users.push(user); user.room = this }
  send(from: User, msg: string): void {
    this.users
      .filter((u) => u !== from)
      .forEach((u) => u.receive(from.name, msg))
  }
}

class User {
  room: ChatRoom | null = null
  constructor(public name: string) {}
  send(msg: string): void { this.room?.send(this, msg) }
  receive(from: string, msg: string): void {
    console.log(`[${this.name}] <- ${from}: ${msg}`)
  }
}

const room = new ChatRoom()
const a = new User('Alice')
const b = new User('Bob')
const c = new User('Carol')
room.join(a); room.join(b); room.join(c)

a.send('hi')
// [Bob]   <- Alice: hi
// [Carol] <- Alice: hi

解释器模式

给一种简单文法定义表达式树和对应的解释器。适合 DSL / 表达式求值;日常较少手写,但模板引擎、查询语言都隐含这种结构。

type Context = Record<string, number>

interface Expr {
  evaluate(ctx?: Context): number
}

class NumberExpr implements Expr {
  constructor(private n: number) {}
  evaluate(): number { return this.n }
}

class VarExpr implements Expr {
  constructor(private name: string) {}
  evaluate(ctx: Context = {}): number { return ctx[this.name] ?? 0 }
}

class AddExpr implements Expr {
  constructor(private l: Expr, private r: Expr) {}
  evaluate(ctx: Context = {}): number {
    return this.l.evaluate(ctx) + this.r.evaluate(ctx)
  }
}

class MulExpr implements Expr {
  constructor(private l: Expr, private r: Expr) {}
  evaluate(ctx: Context = {}): number {
    return this.l.evaluate(ctx) * this.r.evaluate(ctx)
  }
}

// 表示 (x + 2) * 3
const expr = new MulExpr(
  new AddExpr(new VarExpr('x'), new NumberExpr(2)),
  new NumberExpr(3),
)

expr.evaluate({ x: 5 }) // 21
Copyright (c) 2023-PRESENT | wudi