【08】前端常用的设计模式之装饰器模式

62 阅读1分钟

装饰器模式

概念

允许向一个现有对象添加新的功能,同时又不改变其结构。

UML 类图

装饰器模式.png

代码演示

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();
circle.draw();

const decorator = new DecoraTor(circle);
decorator.draw();

场景

ES 引入了 Decorator 语法,TS 也支持

在 tsconfig.json 中加 experimentalDecorators: true

装饰 class

// 装饰器
function testable(target: any) {
  target.isTestable = true;
}

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

console.log(Foo.isTestable); // true

可以传入参数

// 装饰器
function testable(val: boolean) {
  return function (target: any) {
    target.isTestable = val;
  };
}

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

console.log(Foo.isTestable); // false

装饰 class 方法

function readOnly(target: any, key: string, descriptor: PropertyDescriptor) {
  // console.log('target', target);
  // console.log('key', key);
  descriptor.writable = false;
}

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

class Foo {
  private name = 'leslie';
  private age = 20;

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

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

const f = new Foo();
f.getName = () => {
  return 'hi';
}; // 修改不成功
console.log(f.getName()); // leslie

console.log(Object.getOwnPropertyDescriptor(f.__proto__, 'getAge')); // { configurable: false }
console.log(f.getAge);

其实 TS 本身有 readOnly 语法,但这里就是一个演示。

日志 log、react-redux 和 Angular 定义组件

AOP - 面向切面编程

业务和系统基础功能分类,用 Decorator 合适

AOP.png

AOP 和 OOP 并不冲突

实现 log

function log(target: any, key: string, descriptor: PropertyDescriptor) {
  const oldValue = descriptor.value;

  // 重新定义 fn1 函数
  descriptor.value = function () {
    console.log('记录日志...');
    return oldValue.apply(this, arguments);
  };
}

class Foo {
  @log // 不影响业务功能的代码,只是加了一个 log 的切面
  fn1() {
    console.log('业务功能1');
  }
}

const f = new Foo();
f.fn1();

设计原则验证

  • 装饰器和目标分离,解耦
  • 装饰器可自行拓展
  • 目标也可自行拓展