Skip to content

JavaScript 设计模式面试题

1. 请简述什么是设计模式,以及为什么要使用设计模式

Details

设计模式是在软件设计中针对特定问题的可重用解决方案,它是经过验证的、被广泛接受的设计经验的总结。

设计模式的核心概念

  • 模式:在特定环境下解决特定问题的一种方案
  • 可重用:模式可以在不同的场景中重复使用
  • 经过验证:模式是经过实践检验的有效解决方案
  • 最佳实践:模式代表了行业内的最佳实践

为什么要使用设计模式

  1. 提高代码可维护性:设计模式提供了清晰的代码结构,使代码更易于理解和维护。
  2. 提高代码可复用性:设计模式封装了通用的解决方案,可以在不同的项目中重复使用。
  3. 提高代码可靠性:设计模式经过了实践的检验,能够有效解决特定问题。
  4. 促进团队协作:设计模式提供了共同的词汇和理解,使团队成员能够更好地沟通和协作。
  5. 应对变化:设计模式使代码更加灵活,能够更好地应对需求的变化。

设计模式的分类

根据《设计模式:可复用面向对象软件的基础》一书,设计模式可以分为三类:

  1. 创建型模式:处理对象的创建过程,如单例模式、工厂模式、抽象工厂模式、建造者模式、原型模式。
  2. 结构型模式:处理对象之间的组合关系,如适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  3. 行为型模式:处理对象之间的交互和职责分配,如策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

设计模式的应用原则

  1. 不要过度设计:只在必要时使用设计模式,避免为了使用模式而使用模式。
  2. 理解模式的本质:不仅要了解模式的实现,还要理解模式的适用场景和意图。
  3. 灵活应用:根据具体情况灵活调整模式的实现,而不是生搬硬套。
  4. 关注问题本身:设计模式是解决问题的工具,应该先理解问题,再选择合适的模式。

2. 请解释单例模式(Singleton Pattern),并实现一个单例模式

Details

单例模式是一种创建型模式,确保一个类只有一个实例,并提供一个全局访问点。

单例模式的核心思想

  • 唯一性:确保一个类只有一个实例
  • 全局访问:提供一个全局访问点来获取这个实例
  • 延迟初始化:在需要时才创建实例,提高性能

单例模式的适用场景

  • 全局配置:如应用程序的配置对象
  • 资源共享:如数据库连接池、线程池
  • 工具类:如日志记录器、缓存管理器
  • 状态管理:如全局状态管理

实现方式

1. 立即执行函数表达式(IIFE)实现

javascript
const Singleton = (function() {
  let instance;
  
  function createInstance() {
    // 实际的初始化代码
    return {
      name: 'Singleton',
      method: function() {
        console.log('Singleton method');
      }
    };
  }
  
  return {
    getInstance: function() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    }
  };
})();

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

2. 类实现(ES6+)

javascript
class Singleton {
  constructor() {
    if (Singleton.instance) {
      return Singleton.instance;
    }
    
    // 实际的初始化代码
    this.name = 'Singleton';
    Singleton.instance = this;
  }
  
  method() {
    console.log('Singleton method');
  }
  
  static getInstance() {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton();
    }
    return Singleton.instance;
  }
}

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

// 或者使用静态方法
const instance3 = Singleton.getInstance();
console.log(instance1 === instance3); // true

3. 模块模式实现

javascript
// singleton.js
let instance;

function init() {
  // 实际的初始化代码
  return {
    name: 'Singleton',
    method: function() {
      console.log('Singleton method');
    }
  };
}

export default {
  getInstance: function() {
    if (!instance) {
      instance = init();
    }
    return instance;
  }
};

// 使用
import Singleton from './singleton.js';
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true

单例模式的优缺点

优点

  • 节省资源:只创建一个实例,避免了重复创建对象带来的资源消耗
  • 全局访问:提供了一个全局访问点,方便在不同的地方使用同一个实例
  • 一致性:确保所有使用该实例的代码都使用同一个对象,避免了数据不一致的问题

缺点

  • 违反单一职责原则:单例类既要负责创建自己的实例,又要负责业务逻辑
  • 隐藏依赖关系:使用单例模式会使代码的依赖关系不明确,降低代码的可测试性
  • 可能导致内存泄漏:如果单例对象持有大量资源,且在应用程序结束前没有释放,可能会导致内存泄漏
  • 难以扩展:单例模式的扩展性较差,因为它限制了类的实例化

注意事项

  1. 线程安全:在多线程环境下,需要确保单例的创建是线程安全的
  2. 序列化:如果单例类需要序列化,需要确保反序列化后仍然是同一个实例
  3. 全局状态:过度使用单例会导致全局状态过多,增加代码的复杂性
  4. 测试:单例模式会使测试变得困难,因为它难以模拟和替换

3. 请解释工厂模式(Factory Pattern),并实现一个工厂模式

Details

工厂模式是一种创建型模式,它提供了一种创建对象的接口,让子类决定实例化哪一个类。

工厂模式的核心思想

  • 封装创建逻辑:将对象的创建过程封装起来,客户端不需要知道具体的创建细节
  • 统一接口:通过一个统一的接口来创建不同类型的对象
  • 灵活性:可以根据不同的条件创建不同类型的对象

工厂模式的分类

  1. 简单工厂模式:由一个工厂类根据传入的参数决定创建哪一种产品类的实例
  2. 工厂方法模式:定义一个创建对象的接口,让子类决定实例化哪一个类
  3. 抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类

简单工厂模式

实现

javascript
// 产品类
class ProductA {
  operation() {
    return 'Product A operation';
  }
}

class ProductB {
  operation() {
    return 'Product B operation';
  }
}

// 工厂类
class SimpleFactory {
  static createProduct(type) {
    switch (type) {
      case 'A':
        return new ProductA();
      case 'B':
        return new ProductB();
      default:
        throw new Error('Invalid product type');
    }
  }
}

// 使用
const productA = SimpleFactory.createProduct('A');
console.log(productA.operation()); // 'Product A operation'

const productB = SimpleFactory.createProduct('B');
console.log(productB.operation()); // 'Product B operation'

工厂方法模式

实现

javascript
// 产品接口
class Product {
  operation() {
    throw new Error('Subclass must implement operation method');
  }
}

// 具体产品
class ConcreteProductA extends Product {
  operation() {
    return 'ConcreteProduct A operation';
  }
}

class ConcreteProductB extends Product {
  operation() {
    return 'ConcreteProduct B operation';
  }
}

// 工厂接口
class Factory {
  createProduct() {
    throw new Error('Subclass must implement createProduct method');
  }
}

// 具体工厂
class ConcreteFactoryA extends Factory {
  createProduct() {
    return new ConcreteProductA();
  }
}

class ConcreteFactoryB extends Factory {
  createProduct() {
    return new ConcreteProductB();
  }
}

// 使用
const factoryA = new ConcreteFactoryA();
const productA = factoryA.createProduct();
console.log(productA.operation()); // 'ConcreteProduct A operation'

const factoryB = new ConcreteFactoryB();
const productB = factoryB.createProduct();
console.log(productB.operation()); // 'ConcreteProduct B operation'

抽象工厂模式

实现

javascript
// 产品接口
class AbstractProductA {
  operation() {
    throw new Error('Subclass must implement operation method');
  }
}

class AbstractProductB {
  operation() {
    throw new Error('Subclass must implement operation method');
  }
}

// 具体产品
class ConcreteProductA1 extends AbstractProductA {
  operation() {
    return 'ConcreteProduct A1 operation';
  }
}

class ConcreteProductA2 extends AbstractProductA {
  operation() {
    return 'ConcreteProduct A2 operation';
  }
}

class ConcreteProductB1 extends AbstractProductB {
  operation() {
    return 'ConcreteProduct B1 operation';
  }
}

class ConcreteProductB2 extends AbstractProductB {
  operation() {
    return 'ConcreteProduct B2 operation';
  }
}

// 抽象工厂
class AbstractFactory {
  createProductA() {
    throw new Error('Subclass must implement createProductA method');
  }
  
  createProductB() {
    throw new Error('Subclass must implement createProductB method');
  }
}

// 具体工厂
class ConcreteFactory1 extends AbstractFactory {
  createProductA() {
    return new ConcreteProductA1();
  }
  
  createProductB() {
    return new ConcreteProductB1();
  }
}

class ConcreteFactory2 extends AbstractFactory {
  createProductA() {
    return new ConcreteProductA2();
  }
  
  createProductB() {
    return new ConcreteProductB2();
  }
}

// 使用
const factory1 = new ConcreteFactory1();
const productA1 = factory1.createProductA();
const productB1 = factory1.createProductB();
console.log(productA1.operation()); // 'ConcreteProduct A1 operation'
console.log(productB1.operation()); // 'ConcreteProduct B1 operation'

const factory2 = new ConcreteFactory2();
const productA2 = factory2.createProductA();
const productB2 = factory2.createProductB();
console.log(productA2.operation()); // 'ConcreteProduct A2 operation'
console.log(productB2.operation()); // 'ConcreteProduct B2 operation'

工厂模式的优缺点

优点

  • 封装性:将对象的创建过程封装起来,客户端不需要知道具体的创建细节
  • 灵活性:可以根据不同的条件创建不同类型的对象
  • 可维护性:如果需要添加新的产品类型,只需要修改工厂类,不需要修改客户端代码
  • 解耦:将产品的创建与使用分离,降低了代码的耦合度

缺点

  • 增加代码复杂度:引入工厂模式会增加代码的复杂度,需要创建更多的类
  • 难以扩展:如果产品类型很多,会导致工厂类变得非常复杂
  • 违反开闭原则:在简单工厂模式中,添加新的产品类型需要修改工厂类的代码

适用场景

  • 对象创建过程复杂:当对象的创建过程比较复杂,需要很多参数或步骤时
  • 需要创建多种类型的对象:当需要根据不同的条件创建不同类型的对象时
  • 解耦:当需要将对象的创建与使用分离,降低代码耦合度时
  • 可维护性:当需要提高代码的可维护性和可扩展性时

4. 请解释观察者模式(Observer Pattern),并实现一个观察者模式

Details

观察者模式是一种行为型模式,它定义了对象之间的一对多依赖关系,当一个对象状态发生变化时,所有依赖它的对象都会自动收到通知并更新。

观察者模式的核心思想

  • 发布-订阅:主题(Subject)负责发布通知,观察者(Observer)负责订阅通知
  • 松耦合:主题和观察者之间是松耦合的,它们之间通过接口进行通信
  • 自动更新:当主题状态变化时,观察者会自动收到通知并更新

观察者模式的角色

  1. 主题(Subject):维护一组观察者,提供添加和删除观察者的方法,以及通知观察者的方法
  2. 观察者(Observer):定义接收通知的接口,当收到主题的通知时进行更新
  3. 具体主题(Concrete Subject):实现主题接口,维护状态,并在状态变化时通知观察者
  4. 具体观察者(Concrete Observer):实现观察者接口,接收主题的通知并进行相应的处理

实现

javascript
// 主题类
class Subject {
  constructor() {
    this.observers = [];
  }
  
  // 添加观察者
  addObserver(observer) {
    this.observers.push(observer);
  }
  
  // 移除观察者
  removeObserver(observer) {
    const index = this.observers.indexOf(observer);
    if (index > -1) {
      this.observers.splice(index, 1);
    }
  }
  
  // 通知所有观察者
  notify() {
    for (const observer of this.observers) {
      observer.update(this);
    }
  }
}

// 具体主题类
class ConcreteSubject extends Subject {
  constructor() {
    super();
    this.state = 0;
  }
  
  setState(state) {
    this.state = state;
    this.notify(); // 状态变化时通知观察者
  }
  
  getState() {
    return this.state;
  }
}

// 观察者接口
class Observer {
  update(subject) {
    throw new Error('Subclass must implement update method');
  }
}

// 具体观察者类
class ConcreteObserverA extends Observer {
  update(subject) {
    console.log(`ConcreteObserverA received update: ${subject.getState()}`);
  }
}

class ConcreteObserverB extends Observer {
  update(subject) {
    console.log(`ConcreteObserverB received update: ${subject.getState()}`);
  }
}

// 使用
const subject = new ConcreteSubject();

const observerA = new ConcreteObserverA();
const observerB = new ConcreteObserverB();

subject.addObserver(observerA);
subject.addObserver(observerB);

// 改变主题状态,触发通知
subject.setState(1);
// 输出:
// ConcreteObserverA received update: 1
// ConcreteObserverB received update: 1

// 移除一个观察者
subject.removeObserver(observerA);

// 再次改变主题状态
subject.setState(2);
// 输出:
// ConcreteObserverB received update: 2

观察者模式的优缺点

优点

  • 松耦合:主题和观察者之间是松耦合的,它们之间通过接口进行通信
  • 可扩展性:可以轻松添加新的观察者,而不需要修改主题的代码
  • 自动更新:当主题状态变化时,观察者会自动收到通知并更新
  • 广播通信:主题可以同时通知多个观察者,实现一对多的通信

缺点

  • 内存泄漏:如果观察者没有被正确移除,可能会导致内存泄漏
  • 通知顺序:观察者的通知顺序是不确定的,可能会影响业务逻辑
  • 性能问题:如果观察者数量过多,可能会影响通知的性能
  • 循环依赖:如果观察者和主题之间存在循环依赖,可能会导致无限循环

适用场景

  • 事件处理系统:如 DOM 事件、自定义事件
  • 消息队列:如发布-订阅系统
  • 状态管理:如 Redux、Vuex 等状态管理库
  • 数据绑定:如双向数据绑定
  • 监控系统:如系统监控、性能监控

实际应用

  1. DOM 事件:浏览器的 DOM 事件系统就是观察者模式的典型应用
javascript
// 订阅事件
document.getElementById('button').addEventListener('click', function() {
  console.log('Button clicked');
});

// 发布事件(由浏览器触发)
// 当用户点击按钮时,浏览器会触发 click 事件,通知所有订阅者
  1. 自定义事件
javascript
// 自定义事件发射器
class EventEmitter {
  constructor() {
    this.events = {};
  }
  
  on(event, callback) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(callback);
  }
  
  off(event, callback) {
    if (this.events[event]) {
      this.events[event] = this.events[event].filter(cb => cb !== callback);
    }
  }
  
  emit(event, ...args) {
    if (this.events[event]) {
      this.events[event].forEach(callback => callback(...args));
    }
  }
}

// 使用
const emitter = new EventEmitter();

// 订阅事件
emitter.on('message', (data) => {
  console.log('Received message:', data);
});

// 发布事件
emitter.emit('message', 'Hello, world!');
// 输出:Received message: Hello, world!

5. 请解释装饰器模式(Decorator Pattern),并实现一个装饰器模式

Details

装饰器模式是一种结构型模式,它允许向一个现有的对象添加新的功能,而不改变其结构。装饰器模式通过包装原始对象来实现功能的扩展。

装饰器模式的核心思想

  • 动态扩展:在运行时动态地给对象添加额外的功能
  • 包装:通过包装原始对象来实现功能的扩展
  • 透明性:装饰器对象和原始对象具有相同的接口,客户端可以透明地使用装饰器对象
  • 组合:通过组合的方式而不是继承的方式来扩展功能

装饰器模式的角色

  1. 组件(Component):定义对象的接口,可以给这些对象动态地添加责任
  2. 具体组件(Concrete Component):实现组件接口的具体对象
  3. 装饰器(Decorator):持有一个组件对象的引用,并实现与组件相同的接口
  4. 具体装饰器(Concrete Decorator):向组件添加具体的责任

实现

javascript
// 组件接口
class Component {
  operation() {
    throw new Error('Subclass must implement operation method');
  }
}

// 具体组件
class ConcreteComponent extends Component {
  operation() {
    return 'ConcreteComponent operation';
  }
}

// 装饰器
class Decorator extends Component {
  constructor(component) {
    super();
    this.component = component;
  }
  
  operation() {
    return this.component.operation();
  }
}

// 具体装饰器 A
class ConcreteDecoratorA extends Decorator {
  operation() {
    return `ConcreteDecoratorA(${super.operation()})`;
  }
}

// 具体装饰器 B
class ConcreteDecoratorB extends Decorator {
  operation() {
    return `ConcreteDecoratorB(${super.operation()})`;
  }
}

// 使用
const component = new ConcreteComponent();
console.log(component.operation()); // 'ConcreteComponent operation'

// 用装饰器 A 包装
const decoratorA = new ConcreteDecoratorA(component);
console.log(decoratorA.operation()); // 'ConcreteDecoratorA(ConcreteComponent operation)'

// 用装饰器 B 包装装饰器 A
const decoratorB = new ConcreteDecoratorB(decoratorA);
console.log(decoratorB.operation()); // 'ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent operation))'

装饰器模式的优缺点

优点

  • 灵活性:可以动态地给对象添加功能,而不需要修改原始对象的代码
  • 可组合性:可以通过组合多个装饰器来实现复杂的功能
  • 遵循开闭原则:对扩展开放,对修改关闭
  • 替代继承:通过组合的方式而不是继承的方式来扩展功能,避免了继承的缺点

缺点

  • 增加代码复杂度:引入装饰器模式会增加代码的复杂度,需要创建更多的类
  • 嵌套层次:如果装饰器嵌套层次过深,会使代码变得难以理解和维护
  • 性能影响:每次调用装饰器的方法都会调用原始对象的方法,可能会影响性能

适用场景

  • 功能扩展:当需要给对象添加额外的功能,而不修改原始对象的代码时
  • 动态功能:当需要在运行时动态地给对象添加功能时
  • 替代继承:当使用继承会导致类的层次结构过于复杂时
  • 单一职责:当需要将不同的功能分离到不同的装饰器中时

实际应用

  1. ES7 装饰器
javascript
// 类装饰器
function log(target) {
  const original = target;
  
  // 创建一个新的构造函数
  function construct(constructor, args) {
    const c = function() {
      return constructor.apply(this, args);
    };
    c.prototype = constructor.prototype;
    return new c();
  }
  
  const newConstructor = function(...args) {
    console.log(`Creating instance of ${original.name}`);
    return construct(original, args);
  };
  
  newConstructor.prototype = original.prototype;
  return newConstructor;
}

// 方法装饰器
function logMethod(target, name, descriptor) {
  const original = descriptor.value;
  
  descriptor.value = function(...args) {
    console.log(`Calling ${name} with args: ${args}`);
    const result = original.apply(this, args);
    console.log(`${name} returned: ${result}`);
    return result;
  };
  
  return descriptor;
}

// 使用装饰器
@log
class Person {
  constructor(name) {
    this.name = name;
  }
  
  @logMethod
  greet() {
    return `Hello, my name is ${this.name}`;
  }
}

// 使用
const person = new Person('John');
// 输出:Creating instance of Person

person.greet();
// 输出:
// Calling greet with args: 
// greet returned: Hello, my name is John
  1. 中间件
javascript
// 中间件函数
function logger(req, res, next) {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
  next();
}

function auth(req, res, next) {
  const token = req.headers.authorization;
  if (token) {
    console.log('Authenticated');
    next();
  } else {
    res.status(401).send('Unauthorized');
  }
}

function errorHandler(err, req, res, next) {
  console.error(err);
  res.status(500).send('Internal Server Error');
}

// 模拟 Express 应用
class App {
  constructor() {
    this.middleware = [];
  }
  
  use(fn) {
    this.middleware.push(fn);
  }
  
  handle(req, res) {
    let index = 0;
    
    const next = (err) => {
      if (err) {
        // 找到错误处理中间件
        const errorHandler = this.middleware.find(fn => fn.length === 4);
        if (errorHandler) {
          errorHandler(err, req, res, next);
        }
        return;
      }
      
      if (index < this.middleware.length) {
        const fn = this.middleware[index++];
        if (fn.length === 4) {
          // 跳过错误处理中间件
          next();
        } else {
          fn(req, res, next);
        }
      }
    };
    
    next();
  }
}

// 使用
const app = new App();
app.use(logger);
app.use(auth);
app.use(errorHandler);

// 模拟请求
app.handle({ method: 'GET', url: '/api', headers: { authorization: 'token123' } }, { status: (code) => ({ send: (message) => console.log(`Response: ${code} ${message}`) }) });
// 输出:
// 2023-10-01T00:00:00.000Z - GET /api
// Authenticated

6. 请解释策略模式(Strategy Pattern),并实现一个策略模式

Details

策略模式是一种行为型模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。

策略模式的核心思想

  • 封装算法:将不同的算法封装到不同的策略类中
  • 互换性:不同的策略可以相互替换,客户端可以根据需要选择不同的策略
  • 分离变化:将算法的实现与使用分离,使算法的变化不影响客户端

策略模式的角色

  1. 策略接口(Strategy):定义所有支持的算法的公共接口
  2. 具体策略(Concrete Strategy):实现策略接口的具体算法
  3. 上下文(Context):持有一个策略对象的引用,使用策略对象来执行算法

实现

javascript
// 策略接口
class Strategy {
  execute(data) {
    throw new Error('Subclass must implement execute method');
  }
}

// 具体策略 A
class ConcreteStrategyA extends Strategy {
  execute(data) {
    return `ConcreteStrategyA executed with data: ${data}`;
  }
}

// 具体策略 B
class ConcreteStrategyB extends Strategy {
  execute(data) {
    return `ConcreteStrategyB executed with data: ${data}`;
  }
}

// 上下文
class Context {
  constructor(strategy) {
    this.strategy = strategy;
  }
  
  setStrategy(strategy) {
    this.strategy = strategy;
  }
  
  executeStrategy(data) {
    return this.strategy.execute(data);
  }
}

// 使用
const context = new Context(new ConcreteStrategyA());
console.log(context.executeStrategy('test')); // 'ConcreteStrategyA executed with data: test'

// 切换策略
context.setStrategy(new ConcreteStrategyB());
console.log(context.executeStrategy('test')); // 'ConcreteStrategyB executed with data: test'

策略模式的优缺点

优点

  • 灵活性:可以动态地切换算法,而不需要修改客户端代码
  • 可扩展性:可以轻松添加新的策略,而不需要修改现有代码
  • 代码复用:不同的策略可以在不同的场景中重复使用
  • 分离关注点:将算法的实现与使用分离,使代码更加清晰

缺点

  • 增加代码复杂度:引入策略模式会增加代码的复杂度,需要创建更多的类
  • 客户端需要了解策略:客户端需要了解不同的策略,以便选择合适的策略
  • 策略数量:如果策略数量过多,可能会导致代码变得难以管理

适用场景

  • 多种算法选择:当有多种算法可以选择,且客户端需要根据不同的情况选择不同的算法时
  • 算法频繁变化:当算法需要频繁变化,且客户端希望算法的变化不影响自己时
  • 避免条件语句:当使用条件语句来选择不同的算法会导致代码变得复杂时
  • 可测试性:当需要提高代码的可测试性时

实际应用

  1. 排序算法
javascript
// 策略接口
class SortStrategy {
  sort(array) {
    throw new Error('Subclass must implement sort method');
  }
}

// 具体策略:冒泡排序
class BubbleSortStrategy extends SortStrategy {
  sort(array) {
    const arr = [...array];
    for (let i = 0; i < arr.length - 1; i++) {
      for (let j = 0; j < arr.length - i - 1; j++) {
        if (arr[j] > arr[j + 1]) {
          [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
        }
      }
    }
    return arr;
  }
}

// 具体策略:快速排序
class QuickSortStrategy extends SortStrategy {
  sort(array) {
    const arr = [...array];
    if (arr.length <= 1) return arr;
    
    const pivot = arr[Math.floor(arr.length / 2)];
    const left = arr.filter(x => x < pivot);
    const middle = arr.filter(x => x === pivot);
    const right = arr.filter(x => x > pivot);
    
    return [...this.sort(left), ...middle, ...this.sort(right)];
  }
}

// 上下文
class Sorter {
  constructor(strategy) {
    this.strategy = strategy;
  }
  
  setStrategy(strategy) {
    this.strategy = strategy;
  }
  
  sort(array) {
    return this.strategy.sort(array);
  }
}

// 使用
const sorter = new Sorter(new BubbleSortStrategy());
console.log(sorter.sort([3, 1, 4, 1, 5, 9, 2, 6])); // [1, 1, 2, 3, 4, 5, 6, 9]

// 切换策略
const sorter2 = new Sorter(new QuickSortStrategy());
console.log(sorter2.sort([3, 1, 4, 1, 5, 9, 2, 6])); // [1, 1, 2, 3, 4, 5, 6, 9]
  1. 表单验证
javascript
// 策略接口
class ValidationStrategy {
  validate(value) {
    throw new Error('Subclass must implement validate method');
  }
}

// 具体策略:必填验证
class RequiredValidation extends ValidationStrategy {
  validate(value) {
    return value !== undefined && value !== null && value !== '';
  }
}

// 具体策略:邮箱验证
class EmailValidation extends ValidationStrategy {
  validate(value) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(value);
  }
}

// 具体策略:长度验证
class LengthValidation extends ValidationStrategy {
  constructor(min, max) {
    super();
    this.min = min;
    this.max = max;
  }
  
  validate(value) {
    const length = value.length;
    return length >= this.min && length <= this.max;
  }
}

// 上下文
class Validator {
  constructor() {
    this.strategies = {};
  }
  
  addStrategy(field, strategy) {
    if (!this.strategies[field]) {
      this.strategies[field] = [];
    }
    this.strategies[field].push(strategy);
  }
  
  validate(data) {
    const errors = {};
    
    for (const field in this.strategies) {
      const value = data[field];
      const fieldErrors = [];
      
      for (const strategy of this.strategies[field]) {
        if (!strategy.validate(value)) {
          fieldErrors.push(`Invalid ${field}`);
        }
      }
      
      if (fieldErrors.length > 0) {
        errors[field] = fieldErrors;
      }
    }
    
    return {
      isValid: Object.keys(errors).length === 0,
      errors
    };
  }
}

// 使用
const validator = new Validator();

// 添加验证策略
validator.addStrategy('name', new RequiredValidation());
validator.addStrategy('email', new RequiredValidation());
validator.addStrategy('email', new EmailValidation());
validator.addStrategy('password', new RequiredValidation());
validator.addStrategy('password', new LengthValidation(6, 20));

// 验证数据
const result1 = validator.validate({
  name: 'John',
  email: 'john@example.com',
  password: 'password123'
});
console.log(result1); // { isValid: true, errors: {} }

const result2 = validator.validate({
  name: '',
  email: 'invalid-email',
  password: '123'
});
console.log(result2);
// {
//   isValid: false,
//   errors: {
//     name: ['Invalid name'],
//     email: ['Invalid email'],
//     password: ['Invalid password']
//   }
// }

7. 请解释模块模式(Module Pattern),并实现一个模块模式

Details

模块模式是一种设计模式,它允许将代码组织成独立的、可重用的模块,避免全局命名空间污染,提供私有变量和方法,以及公共接口。

模块模式的核心思想

  • 封装:将相关的代码组织在一起,形成一个独立的模块
  • 私有变量:通过闭包创建私有变量和方法,外部无法访问
  • 公共接口:通过返回一个对象来暴露公共接口,外部可以访问
  • 命名空间:避免全局命名空间污染,提高代码的可维护性

实现方式

1. 立即执行函数表达式(IIFE)实现

javascript
const Module = (function() {
  // 私有变量
  let privateVariable = 'private';
  
  // 私有方法
  function privateMethod() {
    console.log('Private method');
    return privateVariable;
  }
  
  // 公共接口
  return {
    publicVariable: 'public',
    publicMethod: function() {
      console.log('Public method');
      // 可以访问私有变量和方法
      console.log(privateMethod());
      return this.publicVariable;
    },
    setPrivateVariable: function(value) {
      privateVariable = value;
    }
  };
})();

// 使用
console.log(Module.publicVariable); // 'public'
console.log(Module.publicMethod()); // 'Public method' -> 'Private method' -> 'private' -> 'public'

// 尝试访问私有变量(会失败)
console.log(Module.privateVariable); // undefined

// 尝试调用私有方法(会失败)
// console.log(Module.privateMethod()); // TypeError: Module.privateMethod is not a function

// 通过公共方法修改私有变量
Module.setPrivateVariable('modified private');
console.log(Module.publicMethod()); // 'Public method' -> 'Private method' -> 'modified private' -> 'public'

2. 带参数的 IIFE 实现

javascript
const Module = (function(dependency) {
  // 私有变量
  let privateVariable = 'private';
  
  // 私有方法
  function privateMethod() {
    console.log('Private method');
    return privateVariable;
  }
  
  // 公共接口
  return {
    publicVariable: 'public',
    publicMethod: function() {
      console.log('Public method');
      console.log(privateMethod());
      // 使用依赖
      console.log(`Using dependency: ${dependency}`);
      return this.publicVariable;
    }
  };
})('dependency value');

// 使用
console.log(Module.publicMethod());
// 输出:
// Public method
// Private method
// private
// Using dependency: dependency value
// public

3. ES6 模块实现

javascript
// module.js
// 私有变量
let privateVariable = 'private';

// 私有方法
function privateMethod() {
  console.log('Private method');
  return privateVariable;
}

// 公共接口
export const publicVariable = 'public';
export function publicMethod() {
  console.log('Public method');
  console.log(privateMethod());
  return publicVariable;
}
export function setPrivateVariable(value) {
  privateVariable = value;
}

// 使用
import { publicVariable, publicMethod, setPrivateVariable } from './module.js';

console.log(publicVariable); // 'public'
console.log(publicMethod()); // 'Public method' -> 'Private method' -> 'private' -> 'public'

// 尝试访问私有变量(会失败)
// console.log(privateVariable); // ReferenceError: privateVariable is not defined

// 尝试调用私有方法(会失败)
// console.log(privateMethod()); // ReferenceError: privateMethod is not defined

// 通过公共方法修改私有变量
setPrivateVariable('modified private');
console.log(publicMethod()); // 'Public method' -> 'Private method' -> 'modified private' -> 'public'

模块模式的优缺点

优点

  • 封装性:将相关的代码组织在一起,形成一个独立的模块
  • 私有变量:通过闭包创建私有变量和方法,外部无法访问,提高了代码的安全性
  • 公共接口:通过返回一个对象来暴露公共接口,外部可以访问,提供了清晰的 API
  • 命名空间:避免全局命名空间污染,提高代码的可维护性
  • 依赖管理:可以通过参数传递依赖,提高代码的可测试性和可维护性

缺点

  • 闭包开销:使用闭包会增加内存开销,因为闭包会保留对外部变量的引用
  • 模块大小:如果模块过大,可能会影响性能
  • 调试困难:私有变量和方法无法在外部直接访问,增加了调试的难度
  • 灵活性:一旦模块被创建,其公共接口就固定了,难以动态修改

适用场景

  • 代码组织:当需要将相关的代码组织在一起,形成一个独立的模块时
  • 封装:当需要封装私有变量和方法,只暴露公共接口时
  • 命名空间:当需要避免全局命名空间污染时
  • 依赖管理:当需要管理模块之间的依赖关系时

实际应用

  1. 工具库
javascript
const Utils = (function() {
  // 私有方法
  function validateEmail(email) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
  
  function validatePhone(phone) {
    const phoneRegex = /^\d{10}$/;
    return phoneRegex.test(phone);
  }
  
  // 公共接口
  return {
    validate: function(type, value) {
      switch (type) {
        case 'email':
          return validateEmail(value);
        case 'phone':
          return validatePhone(value);
        default:
          return false;
      }
    },
    formatCurrency: function(amount) {
      return `$${amount.toFixed(2)}`;
    },
    formatDate: function(date) {
      return new Date(date).toLocaleDateString();
    }
  };
})();

// 使用
console.log(Utils.validate('email', 'john@example.com')); // true
console.log(Utils.validate('phone', '1234567890')); // true
console.log(Utils.formatCurrency(100)); // '$100.00'
console.log(Utils.formatDate(new Date())); // 2023/10/01
  1. 单例服务
javascript
const ApiService = (function() {
  // 私有变量
  const baseUrl = 'https://api.example.com';
  let token = null;
  
  // 私有方法
  function getHeaders() {
    const headers = {
      'Content-Type': 'application/json'
    };
    if (token) {
      headers['Authorization'] = `Bearer ${token}`;
    }
    return headers;
  }
  
  // 公共接口
  return {
    setToken: function(newToken) {
      token = newToken;
    },
    get: async function(endpoint) {
      const response = await fetch(`${baseUrl}${endpoint}`, {
        method: 'GET',
        headers: getHeaders()
      });
      return response.json();
    },
    post: async function(endpoint, data) {
      const response = await fetch(`${baseUrl}${endpoint}`, {
        method: 'POST',
        headers: getHeaders(),
        body: JSON.stringify(data)
      });
      return response.json();
    },
    put: async function(endpoint, data) {
      const response = await fetch(`${baseUrl}${endpoint}`, {
        method: 'PUT',
        headers: getHeaders(),
        body: JSON.stringify(data)
      });
      return response.json();
    },
    delete: async function(endpoint) {
      const response = await fetch(`${baseUrl}${endpoint}`, {
        method: 'DELETE',
        headers: getHeaders()
      });
      return response.json();
    }
  };
})();

// 使用
ApiService.setToken('token123');
ApiService.get('/users')
  .then(data => console.log(data))
  .catch(error => console.error(error));

8. 请解释代理模式(Proxy Pattern),并实现一个代理模式

Details

代理模式是一种结构型模式,它为其他对象提供一种代理以控制对这个对象的访问。代理模式可以在不修改原始对象的情况下,为其添加额外的功能。

代理模式的核心思想

  • 控制访问:通过代理对象控制对原始对象的访问
  • 额外功能:在访问原始对象之前或之后添加额外的功能
  • 透明性:代理对象和原始对象具有相同的接口,客户端可以透明地使用代理对象

代理模式的角色

  1. 抽象主题(Subject):定义代理对象和原始对象的共同接口
  2. 真实主题(Real Subject):实现抽象主题接口,是代理对象所代表的真实对象
  3. 代理(Proxy):实现抽象主题接口,持有真实主题的引用,控制对真实主题的访问

实现

javascript
// 抽象主题
class Subject {
  request() {
    throw new Error('Subclass must implement request method');
  }
}

// 真实主题
class RealSubject extends Subject {
  request() {
    console.log('RealSubject: Handling request');
    return 'RealSubject response';
  }
}

// 代理
class Proxy extends Subject {
  constructor(realSubject) {
    super();
    this.realSubject = realSubject;
  }
  
  request() {
    // 在访问真实主题之前添加额外的功能
    this.beforeRequest();
    
    // 调用真实主题的方法
    const result = this.realSubject.request();
    
    // 在访问真实主题之后添加额外的功能
    this.afterRequest();
    
    return result;
  }
  
  beforeRequest() {
    console.log('Proxy: Before handling request');
  }
  
  afterRequest() {
    console.log('Proxy: After handling request');
  }
}

// 使用
const realSubject = new RealSubject();
const proxy = new Proxy(realSubject);

console.log(proxy.request());
// 输出:
// Proxy: Before handling request
// RealSubject: Handling request
// Proxy: After handling request
// RealSubject response

代理模式的类型

  1. 远程代理:为远程对象提供代理,如网络服务的代理
  2. 虚拟代理:延迟创建开销大的对象,如图片懒加载
  3. 保护代理:控制对原始对象的访问权限,如权限控制
  4. 缓存代理:为开销大的操作结果提供缓存,如计算结果缓存
  5. 智能引用代理:在访问原始对象时执行额外的操作,如计数引用

实际应用

  1. 虚拟代理(图片懒加载)
javascript
// 真实主题:图片加载
class RealImage {
  constructor(src) {
    this.src = src;
    this.loadImage();
  }
  
  loadImage() {
    console.log(`Loading image from ${this.src}`);
    // 实际的图片加载逻辑
  }
  
  display() {
    console.log(`Displaying image from ${this.src}`);
    // 实际的图片显示逻辑
  }
}

// 代理:虚拟代理,延迟加载图片
class ImageProxy {
  constructor(src) {
    this.src = src;
    this.realImage = null;
  }
  
  display() {
    if (!this.realImage) {
      // 延迟创建真实图片对象
      this.realImage = new RealImage(this.src);
    }
    this.realImage.display();
  }
}

// 使用
const image = new ImageProxy('https://example.com/image.jpg');

// 第一次调用 display() 时,会创建 RealImage 实例并加载图片
console.log('First display:');
image.display();
// 输出:
// Loading image from https://example.com/image.jpg
// Displaying image from https://example.com/image.jpg

// 第二次调用 display() 时,直接使用已创建的 RealImage 实例
console.log('\nSecond display:');
image.display();
// 输出:
// Displaying image from https://example.com/image.jpg
  1. 缓存代理(计算结果缓存)
javascript
// 真实主题:计算
class Calculator {
  add(a, b) {
    console.log(`Calculating ${a} + ${b}`);
    // 模拟复杂计算
    for (let i = 0; i < 100000000; i++) {
      // 空循环,模拟计算开销
    }
    return a + b;
  }
}

// 代理:缓存代理,缓存计算结果
class CalculatorProxy {
  constructor(calculator) {
    this.calculator = calculator;
    this.cache = {};
  }
  
  add(a, b) {
    const key = `${a}+${b}`;
    if (this.cache[key]) {
      console.log(`Using cached result for ${a} + ${b}`);
      return this.cache[key];
    }
    const result = this.calculator.add(a, b);
    this.cache[key] = result;
    return result;
  }
}

// 使用
const calculator = new Calculator();
const proxy = new CalculatorProxy(calculator);

// 第一次计算,会执行实际的计算
console.log('First calculation:');
console.log(proxy.add(10, 20)); // 30

// 第二次计算相同的参数,使用缓存
console.log('\nSecond calculation:');
console.log(proxy.add(10, 20)); // 30
  1. ES6 Proxy

ES6 提供了原生的 Proxy 对象,可以更方便地实现代理模式。

javascript
// 原始对象
const target = {
  name: 'John',
  age: 30
};

// 代理对象
const proxy = new Proxy(target, {
  // 拦截获取属性的操作
  get(target, property) {
    console.log(`Getting property ${property}`);
    return target[property];
  },
  
  // 拦截设置属性的操作
  set(target, property, value) {
    console.log(`Setting property ${property} to ${value}`);
    target[property] = value;
    return true;
  },
  
  // 拦截删除属性的操作
  deleteProperty(target, property) {
    console.log(`Deleting property ${property}`);
    delete target[property];
    return true;
  },
  
  // 拦截 in 操作符
  has(target, property) {
    console.log(`Checking if ${property} exists`);
    return property in target;
  },
  
  // 拦截 Object.getOwnPropertyNames() 等操作
  ownKeys(target) {
    console.log('Getting own keys');
    return Object.keys(target);
  }
});

// 使用代理对象
console.log(proxy.name); // Getting property name -> 'John'
proxy.age = 31; // Setting property age to 31
console.log('age' in proxy); // Checking if age exists -> true
delete proxy.name; // Deleting property name
console.log(Object.keys(proxy)); // Getting own keys -> ['age']

代理模式的优缺点

优点

  • 控制访问:可以控制对原始对象的访问,如权限控制、延迟加载等
  • 额外功能:可以在访问原始对象之前或之后添加额外的功能,如日志记录、缓存等
  • 透明性:代理对象和原始对象具有相同的接口,客户端可以透明地使用代理对象
  • 解耦:代理模式将原始对象与额外功能分离,降低了代码的耦合度

缺点

  • 增加代码复杂度:引入代理模式会增加代码的复杂度,需要创建额外的代理类
  • 性能开销:代理模式会增加额外的调用层次,可能会影响性能
  • 维护成本:如果原始对象的接口发生变化,代理对象也需要相应地修改

适用场景

  • 远程访问:当需要访问远程对象时,如网络服务的代理
  • 延迟加载:当需要延迟创建开销大的对象时,如图片懒加载
  • 权限控制:当需要控制对原始对象的访问权限时,如保护代理
  • 缓存:当需要为开销大的操作结果提供缓存时,如计算结果缓存
  • 日志记录:当需要记录对原始对象的访问时,如访问日志
  • 事务管理:当需要在访问原始对象时添加事务管理时,如数据库操作的事务

9. 请解释命令模式(Command Pattern),并实现一个命令模式

Details

命令模式是一种行为型模式,它将请求封装为对象,使请求的发送者和接收者解耦。命令模式允许将请求参数化,队列化,以及支持可撤销操作。

命令模式的核心思想

  • 封装请求:将请求封装为一个对象,包含执行该请求所需的所有信息
  • 解耦:将请求的发送者和接收者解耦,发送者不需要知道接收者的具体实现
  • 可扩展性:可以轻松添加新的命令,而不需要修改现有代码
  • 支持撤销:可以实现命令的撤销和重做操作

命令模式的角色

  1. 命令(Command):定义执行操作的接口
  2. 具体命令(Concrete Command):实现命令接口,包含接收者的引用,并调用接收者的方法来执行请求
  3. 接收者(Receiver):执行命令所对应的操作,是命令的实际执行者
  4. 调用者(Invoker):持有命令对象,并在适当的时候调用命令的执行方法
  5. 客户端(Client):创建具体命令对象,并设置其接收者

实现

javascript
// 接收者
class Receiver {
  action() {
    console.log('Receiver: Performing action');
  }
  
  undoAction() {
    console.log('Receiver: Undoing action');
  }
}

// 命令接口
class Command {
  execute() {
    throw new Error('Subclass must implement execute method');
  }
  
  undo() {
    throw new Error('Subclass must implement undo method');
  }
}

// 具体命令
class ConcreteCommand extends Command {
  constructor(receiver) {
    super();
    this.receiver = receiver;
  }
  
  execute() {
    console.log('ConcreteCommand: Executing command');
    this.receiver.action();
  }
  
  undo() {
    console.log('ConcreteCommand: Undoing command');
    this.receiver.undoAction();
  }
}

// 调用者
class Invoker {
  constructor() {
    this.command = null;
    this.history = [];
  }
  
  setCommand(command) {
    this.command = command;
  }
  
  executeCommand() {
    if (this.command) {
      this.command.execute();
      this.history.push(this.command);
    }
  }
  
  undo() {
    if (this.history.length > 0) {
      const command = this.history.pop();
      command.undo();
    }
  }
}

// 使用
const receiver = new Receiver();
const command = new ConcreteCommand(receiver);
const invoker = new Invoker();

invoker.setCommand(command);
invoker.executeCommand();
// 输出:
// ConcreteCommand: Executing command
// Receiver: Performing action

invoker.undo();
// 输出:
// ConcreteCommand: Undoing command
// Receiver: Undoing action

命令模式的优缺点

优点

  • 解耦:将请求的发送者和接收者解耦,发送者不需要知道接收者的具体实现
  • 可扩展性:可以轻松添加新的命令,而不需要修改现有代码
  • 支持撤销:可以实现命令的撤销和重做操作
  • 支持队列:可以将命令排队执行,实现批处理
  • 支持日志:可以记录命令的执行历史,支持恢复操作

缺点

  • 增加代码复杂度:引入命令模式会增加代码的复杂度,需要创建更多的类
  • 命令对象膨胀:如果有很多命令,会导致命令对象数量激增
  • 性能开销:命令模式会增加额外的调用层次,可能会影响性能

适用场景

  • 需要将请求参数化:当需要将请求封装为对象,以便传递、存储或排队时
  • 需要支持撤销操作:当需要支持命令的撤销和重做操作时
  • 需要支持队列:当需要将命令排队执行,实现批处理时
  • 需要解耦:当需要将请求的发送者和接收者解耦时
  • 需要日志记录:当需要记录命令的执行历史,支持恢复操作时

实际应用

  1. 遥控器
javascript
// 接收者:电视
class TV {
  turnOn() {
    console.log('TV: Turned on');
  }
  
  turnOff() {
    console.log('TV: Turned off');
  }
  
  setChannel(channel) {
    console.log(`TV: Set channel to ${channel}`);
    this.channel = channel;
  }
  
  getChannel() {
    return this.channel || 1;
  }
}

// 命令接口
class Command {
  execute() {
    throw new Error('Subclass must implement execute method');
  }
  
  undo() {
    throw new Error('Subclass must implement undo method');
  }
}

// 具体命令:打开电视
class TurnOnCommand extends Command {
  constructor(tv) {
    super();
    this.tv = tv;
  }
  
  execute() {
    this.tv.turnOn();
  }
  
  undo() {
    this.tv.turnOff();
  }
}

// 具体命令:关闭电视
class TurnOffCommand extends Command {
  constructor(tv) {
    super();
    this.tv = tv;
  }
  
  execute() {
    this.tv.turnOff();
  }
  
  undo() {
    this.tv.turnOn();
  }
}

// 具体命令:设置频道
class SetChannelCommand extends Command {
  constructor(tv, channel) {
    super();
    this.tv = tv;
    this.channel = channel;
    this.previousChannel = tv.getChannel();
  }
  
  execute() {
    this.tv.setChannel(this.channel);
  }
  
  undo() {
    this.tv.setChannel(this.previousChannel);
  }
}

// 调用者:遥控器
class RemoteControl {
  constructor() {
    this.commands = {};
    this.history = [];
  }
  
  setCommand(button, command) {
    this.commands[button] = command;
  }
  
  pressButton(button) {
    if (this.commands[button]) {
      this.commands[button].execute();
      this.history.push(this.commands[button]);
    }
  }
  
  pressUndo() {
    if (this.history.length > 0) {
      const command = this.history.pop();
      command.undo();
    }
  }
}

// 使用
const tv = new TV();
const remote = new RemoteControl();

// 设置命令
remote.setCommand('on', new TurnOnCommand(tv));
remote.setCommand('off', new TurnOffCommand(tv));
remote.setCommand('channel1', new SetChannelCommand(tv, 1));
remote.setCommand('channel2', new SetChannelCommand(tv, 2));

// 执行命令
remote.pressButton('on'); // TV: Turned on
remote.pressButton('channel1'); // TV: Set channel to 1
remote.pressButton('channel2'); // TV: Set channel to 2

// 撤销操作
remote.pressUndo(); // TV: Set channel to 1
remote.pressUndo(); // TV: Turned off
  1. 任务队列
javascript
// 接收者:任务执行器
class TaskExecutor {
  executeTask(task) {
    console.log(`Executing task: ${task.name}`);
    // 实际执行任务的逻辑
    return `Task ${task.name} executed`;
  }
}

// 命令接口
class TaskCommand {
  execute() {
    throw new Error('Subclass must implement execute method');
  }
}

// 具体命令:任务命令
class ConcreteTaskCommand extends TaskCommand {
  constructor(executor, task) {
    super();
    this.executor = executor;
    this.task = task;
  }
  
  execute() {
    return this.executor.executeTask(this.task);
  }
}

// 调用者:任务队列
class TaskQueue {
  constructor() {
    this.queue = [];
    this.executor = new TaskExecutor();
  }
  
  addTask(task) {
    const command = new ConcreteTaskCommand(this.executor, task);
    this.queue.push(command);
  }
  
  processQueue() {
    while (this.queue.length > 0) {
      const command = this.queue.shift();
      command.execute();
    }
  }
}

// 使用
const queue = new TaskQueue();

// 添加任务
queue.addTask({ name: 'Task 1' });
queue.addTask({ name: 'Task 2' });
queue.addTask({ name: 'Task 3' });

// 处理队列
queue.processQueue();
// 输出:
// Executing task: Task 1
// Executing task: Task 2
// Executing task: Task 3

10. 请解释责任链模式(Chain of Responsibility Pattern),并实现一个责任链模式

Details

责任链模式是一种行为型模式,它将请求的发送者和接收者解耦,通过将多个处理对象连成一条链,使请求在链上传递,直到有一个处理对象能够处理该请求。

责任链模式的核心思想

  • 解耦:将请求的发送者和接收者解耦,发送者不需要知道具体的接收者
  • 链式传递:将多个处理对象连成一条链,请求在链上传递
  • 自动处理:请求会自动在链上传递,直到有一个处理对象能够处理它
  • 灵活性:可以动态地调整链的结构,添加或删除处理对象

责任链模式的角色

  1. 抽象处理者(Handler):定义处理请求的接口,包含一个指向下一个处理者的引用
  2. 具体处理者(Concrete Handler):实现抽象处理者接口,处理它能够处理的请求,否则将请求传递给下一个处理者
  3. 客户端(Client):创建处理链,并向链的第一个处理者发送请求

实现

javascript
// 抽象处理者
class Handler {
  constructor() {
    this.nextHandler = null;
  }
  
  setNext(handler) {
    this.nextHandler = handler;
    return handler;
  }
  
  handle(request) {
    if (this.nextHandler) {
      return this.nextHandler.handle(request);
    }
    return null;
  }
}

// 具体处理者 A
class ConcreteHandlerA extends Handler {
  handle(request) {
    if (request === 'A') {
      console.log('ConcreteHandlerA: Handling request A');
      return 'Handled by A';
    } else {
      return super.handle(request);
    }
  }
}

// 具体处理者 B
class ConcreteHandlerB extends Handler {
  handle(request) {
    if (request === 'B') {
      console.log('ConcreteHandlerB: Handling request B');
      return 'Handled by B';
    } else {
      return super.handle(request);
    }
  }
}

// 具体处理者 C
class ConcreteHandlerC extends Handler {
  handle(request) {
    if (request === 'C') {
      console.log('ConcreteHandlerC: Handling request C');
      return 'Handled by C';
    } else {
      return super.handle(request);
    }
  }
}

// 使用
const handlerA = new ConcreteHandlerA();
const handlerB = new ConcreteHandlerB();
const handlerC = new ConcreteHandlerC();

// 设置责任链
handlerA.setNext(handlerB).setNext(handlerC);

// 处理请求
console.log(handlerA.handle('A')); // ConcreteHandlerA: Handling request A -> 'Handled by A'
console.log(handlerA.handle('B')); // ConcreteHandlerB: Handling request B -> 'Handled by B'
console.log(handlerA.handle('C')); // ConcreteHandlerC: Handling request C -> 'Handled by C'
console.log(handlerA.handle('D')); // null (没有处理者能够处理)

责任链模式的优缺点

优点

  • 解耦:将请求的发送者和接收者解耦,发送者不需要知道具体的接收者
  • 灵活性:可以动态地调整链的结构,添加或删除处理对象
  • 可扩展性:可以轻松添加新的处理者,而不需要修改现有代码
  • 单一职责:每个处理者只负责处理自己能够处理的请求,符合单一职责原则

缺点

  • 请求可能不被处理:如果链上没有处理者能够处理请求,请求会被丢弃
  • 性能开销:请求需要在链上传递,可能会影响性能
  • 调试困难:如果链的结构复杂,调试会变得困难
  • 可能导致循环引用:如果链的结构设计不当,可能会导致循环引用

适用场景

  • 多个对象处理同一请求:当有多个对象可以处理同一请求,且具体处理者由运行时决定时
  • 动态处理链:当需要动态地调整处理链的结构时
  • 避免硬编码:当需要避免在代码中硬编码处理逻辑时
  • 分层处理:当需要对请求进行分层处理时

实际应用

  1. 日志系统
javascript
// 抽象处理者
class Logger {
  constructor(level) {
    this.level = level;
    this.nextLogger = null;
  }
  
  setNext(logger) {
    this.nextLogger = logger;
    return logger;
  }
  
  log(message, level) {
    if (this.canHandle(level)) {
      this.write(message);
    }
    if (this.nextLogger) {
      this.nextLogger.log(message, level);
    }
  }
  
  canHandle(level) {
    return this.level <= level;
  }
  
  write(message) {
    throw new Error('Subclass must implement write method');
  }
}

// 具体处理者:控制台日志
class ConsoleLogger extends Logger {
  write(message) {
    console.log(`[CONSOLE] ${message}`);
  }
}

// 具体处理者:文件日志
class FileLogger extends Logger {
  write(message) {
    console.log(`[FILE] ${message}`);
    // 实际写入文件的逻辑
  }
}

// 具体处理者:错误日志
class ErrorLogger extends Logger {
  write(message) {
    console.log(`[ERROR] ${message}`);
    // 实际写入错误日志的逻辑
  }
}

// 日志级别
const LOG_LEVEL = {
  INFO: 1,
  DEBUG: 2,
  ERROR: 3
};

// 使用
const consoleLogger = new ConsoleLogger(LOG_LEVEL.INFO);
const fileLogger = new FileLogger(LOG_LEVEL.DEBUG);
const errorLogger = new ErrorLogger(LOG_LEVEL.ERROR);

// 设置责任链
consoleLogger.setNext(fileLogger).setNext(errorLogger);

// 记录日志
consoleLogger.log('This is an info message', LOG_LEVEL.INFO);
// 输出:
// [CONSOLE] This is an info message
// [FILE] This is an info message
// [ERROR] This is an info message

consoleLogger.log('This is a debug message', LOG_LEVEL.DEBUG);
// 输出:
// [FILE] This is a debug message
// [ERROR] This is a debug message

consoleLogger.log('This is an error message', LOG_LEVEL.ERROR);
// 输出:
// [ERROR] This is an error message
  1. 表单验证
javascript
// 抽象处理者
class Validator {
  constructor() {
    this.nextValidator = null;
  }
  
  setNext(validator) {
    this.nextValidator = validator;
    return validator;
  }
  
  validate(data) {
    const result = this.check(data);
    if (result.isValid && this.nextValidator) {
      return this.nextValidator.validate(data);
    }
    return result;
  }
  
  check(data) {
    throw new Error('Subclass must implement check method');
  }
}

// 具体处理者:必填验证
class RequiredValidator extends Validator {
  constructor(field) {
    super();
    this.field = field;
  }
  
  check(data) {
    if (!data[this.field]) {
      return {
        isValid: false,
        error: `${this.field} is required`
      };
    }
    return { isValid: true };
  }
}

// 具体处理者:邮箱验证
class EmailValidator extends Validator {
  check(data) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(data.email)) {
      return {
        isValid: false,
        error: 'Invalid email format'
      };
    }
    return { isValid: true };
  }
}

// 具体处理者:密码长度验证
class PasswordLengthValidator extends Validator {
  constructor(minLength) {
    super();
    this.minLength = minLength;
  }
  
  check(data) {
    if (data.password.length < this.minLength) {
      return {
        isValid: false,
        error: `Password must be at least ${this.minLength} characters long`
      };
    }
    return { isValid: true };
  }
}

// 使用
const requiredValidator = new RequiredValidator('email');
const emailValidator = new EmailValidator();
const passwordValidator = new PasswordLengthValidator(6);

// 设置责任链
requiredValidator.setNext(emailValidator).setNext(passwordValidator);

// 验证数据
const result1 = requiredValidator.validate({
  email: 'john@example.com',
  password: 'password123'
});
console.log(result1); // { isValid: true }

const result2 = requiredValidator.validate({
  email: '',
  password: '123'
});
console.log(result2); // { isValid: false, error: 'email is required' }

const result3 = requiredValidator.validate({
  email: 'invalid-email',
  password: '123'
});
console.log(result3); // { isValid: false, error: 'Invalid email format' }

const result4 = requiredValidator.validate({
  email: 'john@example.com',
  password: '123'
});
console.log(result4); // { isValid: false, error: 'Password must be at least 6 characters long' }
  1. 中间件
javascript
// 抽象处理者
class Middleware {
  constructor() {
    this.nextMiddleware = null;
  }
  
  setNext(middleware) {
    this.nextMiddleware = middleware;
    return middleware;
  }
  
  handle(req, res, next) {
    this.process(req, res, () => {
      if (this.nextMiddleware) {
        this.nextMiddleware.handle(req, res, next);
      } else if (next) {
        next();
      }
    });
  }
  
  process(req, res, next) {
    throw new Error('Subclass must implement process method');
  }
}

// 具体处理者:日志中间件
class LoggerMiddleware extends Middleware {
  process(req, res, next) {
    console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`);
    next();
  }
}

// 具体处理者:认证中间件
class AuthMiddleware extends Middleware {
  process(req, res, next) {
    const token = req.headers.authorization;
    if (token) {
      console.log('Authenticated');
      next();
    } else {
      res.status(401).send('Unauthorized');
    }
  }
}

// 具体处理者:错误处理中间件
class ErrorHandlerMiddleware extends Middleware {
  process(req, res, next) {
    try {
      next();
    } catch (error) {
      console.error(error);
      res.status(500).send('Internal Server Error');
    }
  }
}

// 使用
const loggerMiddleware = new LoggerMiddleware();
const authMiddleware = new AuthMiddleware();
const errorHandlerMiddleware = new ErrorHandlerMiddleware();

// 设置责任链
loggerMiddleware.setNext(authMiddleware).setNext(errorHandlerMiddleware);

// 模拟请求
const req = {
  method: 'GET',
  url: '/api',
  headers: { authorization: 'token123' }
};

const res = {
  status: (code) => ({ send: (message) => console.log(`Response: ${code} ${message}`) })
};

// 处理请求
loggerMiddleware.handle(req, res);
// 输出:
// 2023-10-01T00:00:00.000Z - GET /api
// Authenticated