Decorator Pattern

装饰者模式的实现与应用

问题

什么是装饰者模式?如何在 JavaScript 中实现?

解答

装饰者模式允许在不修改原有对象的情况下,动态地给对象添加新功能。

基础实现

// 基础组件
class Coffee {
  cost() {
    return 10;
  }

  description() {
    return '咖啡';
  }
}

// 装饰器基类
class CoffeeDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }

  cost() {
    return this.coffee.cost();
  }

  description() {
    return this.coffee.description();
  }
}

// 具体装饰器:加牛奶
class MilkDecorator extends CoffeeDecorator {
  cost() {
    return this.coffee.cost() + 3;
  }

  description() {
    return this.coffee.description() + ' + 牛奶';
  }
}

// 具体装饰器:加糖
class SugarDecorator extends CoffeeDecorator {
  cost() {
    return this.coffee.cost() + 1;
  }

  description() {
    return this.coffee.description() + ' + 糖';
  }
}

// 使用
let coffee = new Coffee();
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);

console.log(coffee.description()); // 咖啡 + 牛奶 + 糖
console.log(coffee.cost()); // 14

函数装饰器

// 装饰器函数:添加日志功能
function withLogging(fn) {
  return function (...args) {
    console.log(`调用 ${fn.name},参数:`, args);
    const result = fn.apply(this, args);
    console.log(`返回值:`, result);
    return result;
  };
}

// 装饰器函数:添加计时功能
function withTiming(fn) {
  return function (...args) {
    const start = performance.now();
    const result = fn.apply(this, args);
    const end = performance.now();
    console.log(`${fn.name} 执行时间: ${end - start}ms`);
    return result;
  };
}

// 原始函数
function add(a, b) {
  return a + b;
}

// 组合装饰
const decoratedAdd = withLogging(withTiming(add));
decoratedAdd(1, 2);

ES7 装饰器语法

// 方法装饰器
function log(target, name, descriptor) {
  const original = descriptor.value;

  descriptor.value = function (...args) {
    console.log(`调用 ${name},参数:`, args);
    return original.apply(this, args);
  };

  return descriptor;
}

// 类装饰器
function readonly(target, name, descriptor) {
  descriptor.writable = false;
  return descriptor;
}

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

  @readonly
  pi() {
    return 3.14159;
  }
}

const calc = new Calculator();
calc.add(1, 2); // 调用 add,参数: [1, 2]

实际应用:防抖装饰器

function debounce(delay) {
  return function (target, name, descriptor) {
    const original = descriptor.value;
    let timer = null;

    descriptor.value = function (...args) {
      clearTimeout(timer);
      timer = setTimeout(() => {
        original.apply(this, args);
      }, delay);
    };

    return descriptor;
  };
}

class SearchBox {
  @debounce(300)
  search(keyword) {
    console.log('搜索:', keyword);
  }
}

关键点

  • 装饰者模式在不修改原对象的前提下扩展功能,符合开闭原则
  • 装饰器可以嵌套组合,灵活添加多个功能
  • 函数式装饰器通过高阶函数实现,返回包装后的新函数
  • ES7 装饰器是语法糖,本质是在定义时修改类或方法的描述符
  • 常见应用:日志、缓存、权限校验、防抖节流