Skip to content

装饰器模式

行为型模式

特点

装饰器模式的核心在于通过组合而非继承来扩展对象的功能,符合开闭原则。

  • 针对一个对象
  • 动态地添加新功能
  • 同时保留其原有的功能

基本使用

代码

ts
class Circle {
  draw() {
    console.log('画圆');
  }
}

class Decorator {
  private circle: Circle;

  constructor(circle: Circle) {
    this.circle = circle;
  }

  draw() {
    this.circle.draw(); // 原有功能
    this.setBorder(); // 装饰
  }

  private setBorder() {
    console.log('设置边框');
  }
}

const circle = new Circle();
const decorator = new Decorator(circle);
decorator.draw();

使用 UML 图表示

alt text

使用场景

装饰器模式适用于需要动态扩展对象功能的场景,如日志记录、权限控制等。

建议使用 bun 运行 ts 代码

  • 装饰 class
ts
// 装饰器函数
// function testable(target: any) {
//   target.isTestable = true;
// }

// 装饰器的工厂函数(工厂模式思想)
function testable(val: boolean) {
  return function (target: any) {
    target.isTestable = val;
  };
}

@testable(false)
class Foo {
  static isTestable?: boolean;
}

console.log(Foo.isTestable);
  • 装饰 method
ts
/**
 * 只读装饰器
 * @param target 实例
 * @param key key
 * @param descriptor 属性描述符
 */
function readOnly(target: any, key: string, descriptor: PropertyDescriptor) {
  descriptor.writable = false;
}

function configurable(val: boolean) {
  return function (target: any, key: string, descriptor: PropertyDescriptor) {
    descriptor.configurable = val;
  };
}

class Foo {
  private name = '张三';
  private age = 18;

  @readOnly
  getName() {
    return this.name;
  }

  @configurable(false)
  getAge() {
    return this.age;
  }
}

const foo = new Foo();

// 直接报错了
foo.getName = () => {
  console.log('修改了');
};

/* {
  value: [Function: getAge],
  writable: true,
  enumerable: false,
  configurable: false,
}*/
console.log(Object.getOwnPropertyDescriptor(foo.__proto__, 'getAge'));

AOP

AOP 通过装饰器模式实现横切关注点的分离,使得业务逻辑与系统功能解耦。

  • Aspect Oriented Program 面向切面编程
  • 业务和系统基础功能分离,和 Decorator 很配
  • AOP 和 OOP 并不冲突,主要用于日志、事务、缓存、权限、性能监控等

普通写法

ts
// 普通写法
function log1() {
  console.log('记录日志');
}

class Foo1 {
  fn() {
    log1();
    console.log('业务功能-点赞');
  }
}
const foo1 = new Foo1();
console.log(foo1.fn());

装饰器写法

ts
// 装饰器写法
function log2(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor,
) {
  const oldMethod = descriptor.value; // 保存原有 fn 方法

  // 重写 Foo2.fn 方法
  descriptor.value = function () {
    console.log('记录日志');
    return oldMethod.apply(this, arguments);
  };

  return descriptor;
}

class Foo2 {
  @log2
  fn() {
    console.log('业务功能-点赞');
  }
}

const foo2 = new Foo2();
console.log(foo2.fn());

基于 MIT 许可发布