JavaScript 探索之路:JavaScript 中的设计模式

127 阅读6分钟

引言

设计模式是软件开发中的通用解决方案,它们能够帮助开发者设计更灵活、可维护和扩展的代码结构。JavaScript 中有一系列经典的设计模式,本文将介绍 15 种常见的设计模式,讨论它们的优缺点、实际应用案例、组合使用方式、性能考量、错误处理以及在现代 JavaScript 框架中的应用。同时,我们还会探讨如何通过这些设计模式来遵循 SOLID 原则。

在深入学习设计模式之前,建议先阅读《深入理解 SOLID 原则:JavaScript 开发中的应用与实践》,以便更好地理解设计模式在软件架构中的重要性以及如何与 SOLID 原则结合使用。以下是文章链接:
深入理解 SOLID 原则:JavaScript 开发中的应用与实践


1. 单例模式(Singleton Pattern)

模式概述

单例模式确保一个类只有一个实例,并提供一个全局访问点,常用于全局状态管理,如配置、日志记录、缓存等。

实际应用案例

在 Redux 中,store 是单例模式的一个实际应用,它确保整个应用只有一个状态管理实例。

代码示例

class Singleton {
  constructor() {
    if (!Singleton.instance) {
      Singleton.instance = this;
    }
    return Singleton.instance;
  }
}

// 防止修改单例
Object.freeze(Singleton);

优缺点

  • 优点: 全局访问,节省资源,确保状态统一。
  • 缺点: 难以调试和测试,可能导致紧耦合。

SOLID 原则关联

  • 单一职责原则: 单例模式可能违反此原则,因为它可能会承担过多的职责。
  • 依赖倒置原则: 通过依赖注入,可以避免单例模式造成的紧耦合。

避免过度设计

在不需要全局状态的场景下,不应强行使用单例模式,以避免复杂性。


2. 工厂模式(Factory Pattern)

模式概述

工厂模式通过抽象化对象创建过程,使得代码更具灵活性,常用于创建具有相似属性的不同对象。

实际应用案例

在 Angular 中,依赖注入机制常利用工厂模式来创建服务实例。

代码示例

class Car {
  constructor() {
    this.type = "Car";
  }
}

class Truck {
  constructor() {
    this.type = "Truck";
  }
}

class VehicleFactory {
  createVehicle(type) {
    switch (type) {
      case "Car":
        return new Car();
      case "Truck":
        return new Truck();
      default:
        throw new Error("Unknown vehicle type");
    }
  }
}

组合使用

工厂模式常与策略模式结合使用,以动态创建不同策略的对象。

性能考量

在高频对象创建的场景下,工厂模式需要注意性能优化,如使用对象池技术。

SOLID 原则关联

  • 开闭原则: 工厂模式使代码对扩展开放,对修改封闭。
  • 依赖倒置原则: 工厂模式通过抽象,降低了高层模块对底层模块的依赖。

3. 策略模式(Strategy Pattern)

模式概述

策略模式允许定义一系列算法,并将它们独立封装以实现互换。常用于替代冗长的条件语句。

实际应用案例

在支付系统中,策略模式可用于实现不同的支付方式,如信用卡支付、PayPal 支付等。

代码示例

class PaymentContext {
  constructor(strategy) {
    this.strategy = strategy;
  }

  pay(amount) {
    return this.strategy.execute(amount);
  }
}

class CreditCardPayment {
  execute(amount) {
    return `Paid ${amount} using Credit Card`;
  }
}

class PayPalPayment {
  execute(amount) {
    return `Paid ${amount} using PayPal`;
  }
}

组合使用

策略模式可与工厂模式结合使用,通过工厂创建具体策略对象。

错误处理

确保在策略选择时,能处理无效或未知策略的情况。

SOLID 原则关联

  • 开闭原则: 新的策略可以添加,而无需修改现有代码。
  • 单一职责原则: 每个策略仅负责一个具体算法的实现。

4. 观察者模式(Observer Pattern)

模式概述

观察者模式定义对象间的一对多依赖关系,实现事件驱动的通知机制。广泛应用于事件系统中。

实际应用案例

在 Vue.js 中,响应式数据绑定和事件机制就是观察者模式的实现。

代码示例

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

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

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

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

组合使用

观察者模式可与中介者模式结合使用,通过中介者来协调通知流程。

性能考量

在有大量观察者时,应考虑异步通知,以减少性能开销。

SOLID 原则关联

  • 依赖倒置原则: 观察者模式使得具体观察者与主题之间没有直接依赖。
  • 开闭原则: 可以轻松增加或删除观察者,而不影响其他部分。

5. 装饰器模式(Decorator Pattern)

模式概述

装饰器模式允许动态地为对象添加行为而不影响现有代码,常用于增强类的功能。

实际应用案例

在 React 中,高阶组件(HOC)是一种装饰器模式的应用。

代码示例

class Coffee {
  cost() {
    return 5;
  }
}

class MilkDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }
  cost() {
    return this.coffee.cost() + 2;
  }
}

组合使用

装饰器模式常与组合模式结合,提供更复杂的行为组合。

错误处理

确保装饰器不会改变原始对象的接口或行为,避免不可预期的错误。

SOLID 原则关联

  • 开闭原则: 可以通过添加装饰器扩展对象功能,而不修改原始对象。
  • 单一职责原则: 每个装饰器只添加一个功能,职责明确。

6. 代理模式(Proxy Pattern)

模式概述

代理模式为另一个对象提供代理或占位符,以控制对其访问。常用于延迟加载、缓存和权限控制。

实际应用案例

在 Vue.js 中,v-model 双向绑定背后的实现利用了代理模式。

代码示例

class RealImage {
  display() {
    console.log("Displaying image");
  }
}

class ProxyImage {
  constructor() {
    this.realImage = new RealImage();
  }
  display() {
    console.log("Performing proxy logic");
    this.realImage.display();
  }
}

性能考量

代理模式可通过延迟加载和缓存机制提高性能,但要避免过度代理引入的复杂性。

SOLID 原则关联

  • 依赖倒置原则: 客户端依赖于抽象代理,而不是具体实现。
  • 单一职责原则: 代理对象专注于访问控制,实际工作由目标对象处理。

7. 迭代器模式(Iterator Pattern)

模式概述

迭代器模式提供一种方法顺序访问集合对象中的元素,而无需暴露其内部表示。常用于遍历复杂数据结构。

实际应用案例

JavaScript 内置的 for...of 语句就是对迭代器模式的应用。

代码示例

class Iterator {
  constructor(items) {
    this.items = items;
    this.index = 0;
  }
  next() {
    return this.index < this.items.length ? this.items[this.index++] : null;
  }
}

错误处理

处理遍历结束的情况,确保迭代器不会引发错误。

SOLID 原则关联

  • 单一职责原则: 迭代器负责遍历集合,集合负责存储数据,职责明确。

8. 适配器模式(Adapter Pattern)

模式概述

适配器模式用于将一个类的接口转换为另一个接口,以兼容不同的代码,常用于解决代码兼容性问题。

实际应用案例

在现代 Web 开发中,适配器模式常用于统一不同 API 的接口。

代码示例

class OldInterface {
  oldRequest() {
    return "Old interface";
  }
}

class NewInterface {
  request() {
    return "New interface";
  }
}

class Adapter {
  constructor(oldInterface) {
    this.oldInterface = oldInterface;
  }

  request() {
    return this.oldInterface.oldRequest();
  }
}

组合使用

适配器模式与工厂模式常结合使用,以在新旧系统中桥接不兼容

的接口。

SOLID 原则关联

  • 依赖倒置原则: 适配器使得新代码依赖于抽象接口,而非具体实现。

9. 外观模式(Facade Pattern)

模式概述

外观模式通过提供一个统一的接口,使得复杂系统更易于使用。常用于简化复杂 API 的使用。

实际应用案例

在浏览器 DOM 操作中,jQuery 提供了一个外观模式的简化接口。

代码示例

class ComplexSubsystemA {
  operationA() {
    return "Subsystem A, Operation A";
  }
}

class ComplexSubsystemB {
  operationB() {
    return "Subsystem B, Operation B";
  }
}

class Facade {
  constructor() {
    this.subsystemA = new ComplexSubsystemA();
    this.subsystemB = new ComplexSubsystemB();
  }

  operation() {
    return `${this.subsystemA.operationA()} + ${this.subsystemB.operationB()}`;
  }
}

性能考量

外观模式在封装复杂逻辑时,应关注性能开销,避免封装过度影响效率。

SOLID 原则关联

  • 依赖倒置原则: 客户端依赖于外观接口,而不是具体子系统。
  • 单一职责原则: 外观模式简化了对外接口,避免了直接操作子系统的复杂性。

10. 组合模式(Composite Pattern)

模式概述

组合模式允许将对象组合成树形结构来表示部分-整体的层次结构,常用于处理具有递归结构的数据。

实际应用案例

React 组件树就是一种组合模式的应用,每个组件既是部分,又是整体。

代码示例

class Component {
  operation() {
    throw new Error("This method should be overwritten");
  }
}

class Leaf extends Component {
  operation() {
    return "Leaf";
  }
}

class Composite extends Component {
  constructor() {
    super();
    this.children = [];
  }

  add(component) {
    this.children.push(component);
  }

  operation() {
    return this.children.map((child) => child.operation()).join(" + ");
  }
}

组合使用

组合模式可与装饰器模式结合,扩展树形结构的功能。

错误处理

在树形结构中,确保操作不会因递归调用导致堆栈溢出。

SOLID 原则关联

  • 单一职责原则: 每个组件负责其自身的行为,实现职责分离。

11. 桥接模式(Bridge Pattern)

模式概述

桥接模式将抽象部分与实现部分分离,使它们可以独立变化。常用于应对复杂系统中的多维变化。

实际应用案例

在跨平台应用中,桥接模式可以用于分离平台无关的逻辑与具体平台实现。

代码示例

class Shape {
  constructor(color) {
    this.color = color;
  }

  applyColor() {
    throw new Error("This method should be overwritten");
  }
}

class Circle extends Shape {
  applyColor() {
    return `Circle colored with ${this.color}`;
  }
}

class Square extends Shape {
  applyColor() {
    return `Square colored with ${this.color}`;
  }
}

SOLID 原则关联

  • 依赖倒置原则: 抽象部分依赖于接口,而非具体实现。

12. 模板方法模式(Template Method Pattern)

模式概述

模板方法模式定义了一个算法的骨架,允许子类在不改变结构的情况下重新定义某些步骤。常用于代码重用和标准化流程。

实际应用案例

在数据处理管道中,模板方法模式可用于标准化处理步骤,同时允许自定义部分实现。

代码示例

class DataProcessor {
  process() {
    this.readData();
    this.transformData();
    this.saveData();
  }

  readData() {
    throw new Error("This method should be overwritten");
  }

  transformData() {
    throw new Error("This method should be overwritten");
  }

  saveData() {
    throw new Error("This method should be overwritten");
  }
}

class JSONDataProcessor extends DataProcessor {
  readData() {
    console.log("Reading JSON data");
  }

  transformData() {
    console.log("Transforming JSON data");
  }

  saveData() {
    console.log("Saving JSON data");
  }
}

组合使用

模板方法模式可与策略模式结合,灵活定义算法步骤。

SOLID 原则关联

  • 开闭原则: 子类可以扩展模板方法的步骤,而不改变原有的算法结构。

13. 生成器模式(Builder Pattern)

模式概述

生成器模式通过逐步构建复杂对象来简化对象创建过程,常用于创建具有多个可选参数的对象。

实际应用案例

在表单生成和配置项初始化中,生成器模式能够显著简化代码。

代码示例

class CarBuilder {
  constructor() {
    this.car = new Car();
  }

  setEngine(engine) {
    this.car.engine = engine;
    return this;
  }

  setWheels(wheels) {
    this.car.wheels = wheels;
    return this;
  }

  build() {
    return this.car;
  }
}

组合使用

生成器模式常与工厂模式结合,以简化复杂对象的创建。

SOLID 原则关联

  • 单一职责原则: 生成器模式将对象的构建过程与其表示分离。

14. 原型模式(Prototype Pattern)

模式概述

原型模式通过复制现有对象来创建新对象,常用于性能优化和对象克隆。

实际应用案例

在游戏开发中,原型模式常用于快速创建相似对象,如游戏角色、道具等。

代码示例

class Prototype {
  constructor(name) {
    this.name = name;
  }

  clone() {
    return new Prototype(this.name);
  }
}

性能考量

原型模式可以通过浅复制或深复制来提高性能,但需要注意数据引用问题。

SOLID 原则关联

  • 依赖倒置原则: 原型模式减少了对具体类的依赖,使代码更灵活。

15. 状态模式(State Pattern)

模式概述

状态模式允许对象在内部状态改变时改变其行为,常用于管理复杂的状态转换。

实际应用案例

在 Redux 中,状态模式用于管理应用的全局状态,通过 dispatch 动作改变状态。

代码示例

class Context {
  constructor() {
    this.state = null;
  }

  setState(state) {
    this.state = state;
  }

  request() {
    this.state.handle(this);
  }
}

class State {
  handle(context) {
    console.log("Handling request in State");
  }
}

组合使用

状态模式常与策略模式结合,实现复杂的状态逻辑管理。

SOLID 原则关联

  • 开闭原则: 可以轻松添加新的状态类,而无需修改现有代码。

结论

JavaScript 中的设计模式为开发者提供了一整套解决方案,帮助编写更清晰、可维护的代码。在实际应用中,设计模式的选择应根据具体需求和场景进行,避免过度设计,并结合 SOLID 原则进行代码架构设计。

社区与资源