Skip to content

JavaScript 设计模式指南

设计模式优先级排序

1. 单例模式

核心思想

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

优点

  • 确保全局只有一个实例,避免资源浪费
  • 提供统一的访问点,方便管理
  • 可以延迟初始化,提高性能

缺点

  • 违反单一职责原则,同时负责创建和管理实例
  • 可能导致模块间的紧耦合
  • 测试困难,因为单例是全局状态

使用场景

  • 全局配置对象
  • 数据库连接池
  • 日志记录器
  • 缓存

代码示例

javascript
class Singleton {
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance
    }
    this.data = 'Singleton instance'
    Singleton.instance = this
    return this
  }

  getData() {
    return this.data
  }
}

// 使用
const instance1 = new Singleton()
const instance2 = new Singleton()
console.log(instance1 === instance2) // true

2. 工厂模式

核心思想

通过工厂方法创建对象,而不是直接使用构造函数。

优点

  • 封装对象创建过程,隐藏实现细节
  • 便于统一管理对象创建
  • 支持多态,根据条件创建不同类型的对象

缺点

  • 增加了代码复杂度
  • 可能导致类的数量增加

使用场景

  • 复杂对象的创建
  • 需要根据不同条件创建不同类型对象的场景
  • 框架和库的设计

代码示例

javascript
class Product {
  constructor(name) {
    this.name = name
  }

  getInfo() {
    return `Product: ${this.name}`
  }
}

class ProductFactory {
  static createProduct(type) {
    switch (type) {
      case 'electronics':
        return new Product('Electronics')
      case 'clothing':
        return new Product('Clothing')
      case 'food':
        return new Product('Food')
      default:
        throw new Error('Invalid product type')
    }
  }
}

// 使用
const electronics = ProductFactory.createProduct('electronics')
console.log(electronics.getInfo()) // Product: Electronics

3. 观察者模式

核心思想

定义对象间的一种一对多依赖关系,当一个对象状态发生变化时,所有依赖它的对象都得到通知并自动更新。

优点

  • 实现松耦合,观察者和被观察者之间没有直接依赖
  • 支持广播通信,一个对象的变化可以通知多个对象
  • 符合开闭原则,新增观察者无需修改原有代码

缺点

  • 如果观察者太多,通知可能会比较耗时
  • 可能导致循环依赖
  • 观察者只知道被观察者发生了变化,但不知道具体发生了什么变化

使用场景

  • 事件处理系统
  • 消息通知系统
  • 数据绑定
  • 发布/订阅系统

代码示例

javascript
class Subject {
  constructor() {
    this.observers = []
  }

  subscribe(observer) {
    this.observers.push(observer)
  }

  unsubscribe(observer) {
    this.observers = this.observers.filter(obs => obs !== observer)
  }

  notify(data) {
    this.observers.forEach(observer => observer.update(data))
  }
}

class Observer {
  update(data) {
    console.log(`Observer received data: ${data}`)
  }
}

// 使用
const subject = new Subject()
const observer1 = new Observer()
const observer2 = new Observer()

subject.subscribe(observer1)
subject.subscribe(observer2)
subject.notify('Hello World!')
// Output:
// Observer received data: Hello World!
// Observer received data: Hello World!

4. 装饰器模式

核心思想

动态地给对象添加额外的职责,而不修改其原始结构。

优点

  • 比继承更灵活,可以动态添加或移除职责
  • 遵循单一职责原则,每个装饰器只负责一个功能
  • 可以组合多个装饰器,实现复杂功能

缺点

  • 可能导致代码结构复杂,难以理解
  • 装饰器链可能过长

使用场景

  • 功能扩展
  • 权限控制
  • 日志记录
  • 缓存

代码示例

javascript
class Component {
  operation() {
    return 'Component operation'
  }
}

class Decorator {
  constructor(component) {
    this.component = component
  }

  operation() {
    return this.component.operation()
  }
}

class LoggingDecorator extends Decorator {
  operation() {
    console.log('Logging before operation')
    const result = super.operation()
    console.log('Logging after operation')
    return result
  }
}

class CachingDecorator extends Decorator {
  constructor(component) {
    super(component)
    this.cache = {}
  }

  operation() {
    const key = 'operation'
    if (!this.cache[key]) {
      this.cache[key] = super.operation()
    }
    return this.cache[key]
  }
}

// 使用
const component = new Component()
const loggingComponent = new LoggingDecorator(component)
const cachingComponent = new CachingDecorator(loggingComponent)
console.log(cachingComponent.operation())

5. 模块模式

核心思想

使用闭包创建私有变量和方法,只暴露公共接口。

优点

  • 实现封装,保护私有变量
  • 避免全局命名空间污染
  • 支持模块化开发

缺点

  • 模块一旦加载,无法动态修改
  • 可能导致内存泄漏

使用场景

  • 模块化开发
  • 库和框架的设计
  • 避免全局变量污染

代码示例

javascript
const Module = (function () {
  // 私有变量
  let privateVar = 0

  // 私有方法
  function privateMethod() {
    return 'Private method'
  }

  // 公共接口
  return {
    publicVar: 1,
    publicMethod: function () {
      privateVar++
      return `Public method, privateVar: ${privateVar}, ${privateMethod()}`
    }
  }
})()

// 使用
console.log(Module.publicVar) // 1
console.log(Module.publicMethod()) // Public method, privateVar: 1, Private method
console.log(Module.privateVar) // undefined

6. 代理模式

核心思想

为其他对象提供一种代理以控制对这个对象的访问。

优点

  • 可以在访问对象前进行预处理
  • 可以控制访问权限
  • 可以实现延迟加载

缺点

  • 增加了代码复杂度
  • 可能导致请求处理速度变慢

使用场景

  • 权限控制
  • 远程代理
  • 虚拟代理(延迟加载)
  • 缓存代理

代码示例

javascript
class RealSubject {
  request() {
    return 'RealSubject request'
  }
}

class Proxy {
  constructor(realSubject) {
    this.realSubject = realSubject
  }

  request() {
    // 预处理
    console.log('Proxy: Preprocessing request')
    // 调用真实对象
    const result = this.realSubject.request()
    // 后处理
    console.log('Proxy: Postprocessing request')
    return result
  }
}

// 使用
const realSubject = new RealSubject()
const proxy = new Proxy(realSubject)
console.log(proxy.request())
// Output:
// Proxy: Preprocessing request
// RealSubject request
// Proxy: Postprocessing request

7. 命令模式

核心思想

将请求封装为对象,使得可以用不同的请求参数化客户端。

优点

  • 解耦发送者和接收者
  • 支持命令队列和撤销操作
  • 便于扩展新命令

缺点

  • 可能导致类的数量增加
  • 命令对象可能会变得复杂

使用场景

  • 撤销/重做操作
  • 队列请求
  • 事务处理
  • 日志记录

代码示例

javascript
class Command {
  execute() {
    throw new Error('Subclass must implement execute method')
  }

  undo() {
    throw new Error('Subclass must implement undo method')
  }
}

class LightOnCommand extends Command {
  constructor(light) {
    super()
    this.light = light
  }

  execute() {
    this.light.turnOn()
  }

  undo() {
    this.light.turnOff()
  }
}

class Light {
  turnOn() {
    console.log('Light is on')
  }

  turnOff() {
    console.log('Light is off')
  }
}

class RemoteControl {
  setCommand(command) {
    this.command = command
  }

  pressButton() {
    this.command.execute()
  }

  pressUndo() {
    this.command.undo()
  }
}

// 使用
const light = new Light()
const lightOnCommand = new LightOnCommand(light)
const remote = new RemoteControl()

remote.setCommand(lightOnCommand)
remote.pressButton() // Light is on
remote.pressUndo() // Light is off

8. 策略模式

核心思想

定义一系列算法,把它们封装起来,并且使它们可以互相替换。

优点

  • 算法可以独立于使用它的客户端变化
  • 避免使用大量的条件语句
  • 符合开闭原则

缺点

  • 客户端必须了解所有策略
  • 策略类可能会变得很多

使用场景

  • 不同算法的选择
  • 排序算法
  • 支付方式选择
  • 验证规则

代码示例

javascript
class Strategy {
  execute() {
    throw new Error('Subclass must implement execute method')
  }
}

class AddStrategy extends Strategy {
  execute(a, b) {
    return a + b
  }
}

class SubtractStrategy extends Strategy {
  execute(a, b) {
    return a - b
  }
}

class Context {
  constructor(strategy) {
    this.strategy = strategy
  }

  setStrategy(strategy) {
    this.strategy = strategy
  }

  executeStrategy(a, b) {
    return this.strategy.execute(a, b)
  }
}

// 使用
const context = new Context(new AddStrategy())
console.log(context.executeStrategy(10, 5)) // 15

context.setStrategy(new SubtractStrategy())
console.log(context.executeStrategy(10, 5)) // 5

9. 适配器模式

核心思想

将一个类的接口转换成客户希望的另一个接口。

优点

  • 使不兼容的接口可以一起工作
  • 提高代码复用性
  • 符合开闭原则

缺点

  • 增加了代码复杂度
  • 可能会导致性能损失

使用场景

  • 集成第三方库
  • legacy 代码适配
  • 不同系统间的接口转换

代码示例

javascript
// 旧接口
class OldCalculator {
  operations(t1, t2, operation) {
    switch (operation) {
      case 'add':
        return t1 + t2
      case 'subtract':
        return t1 - t2
      default:
        return NaN
    }
  }
}

// 新接口
class NewCalculator {
  add(t1, t2) {
    return t1 + t2
  }

  subtract(t1, t2) {
    return t1 - t2
  }
}

// 适配器
class CalculatorAdapter {
  constructor() {
    this.calculator = new NewCalculator()
  }

  operations(t1, t2, operation) {
    switch (operation) {
      case 'add':
        return this.calculator.add(t1, t2)
      case 'subtract':
        return this.calculator.subtract(t1, t2)
      default:
        return NaN
    }
  }
}

// 使用
const oldCalc = new OldCalculator()
console.log(oldCalc.operations(10, 5, 'add')) // 15

const adapter = new CalculatorAdapter()
console.log(adapter.operations(10, 5, 'add')) // 15

10. 外观模式

核心思想

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

优点

  • 简化客户端代码
  • 减少客户端与子系统之间的依赖
  • 提高系统的可维护性

缺点

  • 可能会隐藏子系统的复杂性,导致客户端无法充分利用子系统的功能
  • 可能会成为系统中的瓶颈

使用场景

  • 复杂系统的简化接口
  • 库和框架的设计
  • 遗留系统的包装

代码示例

javascript
// 子系统
class Subsystem1 {
  operation1() {
    return 'Subsystem1 operation1'
  }
}

class Subsystem2 {
  operation2() {
    return 'Subsystem2 operation2'
  }
}

// 外观
class Facade {
  constructor() {
    this.subsystem1 = new Subsystem1()
    this.subsystem2 = new Subsystem2()
  }

  operation() {
    return `${this.subsystem1.operation1()} + ${this.subsystem2.operation2()}`
  }
}

// 使用
const facade = new Facade()
console.log(facade.operation()) // Subsystem1 operation1 + Subsystem2 operation2

总结

设计模式是解决软件设计中常见问题的可重用方案。在 JavaScript 开发中,选择合适的设计模式可以:

  1. 提高代码的可维护性和可扩展性
  2. 促进代码复用
  3. 改善代码结构和可读性
  4. 帮助团队成员之间的沟通

选择设计模式时,应根据具体的业务需求和代码场景,权衡各种模式的优缺点,选择最适合的解决方案。记住,设计模式不是银弹,过度使用或不当使用可能会导致代码变得复杂和难以理解。

基于 VitePress 的本地知识库