组件库项目中运用的装饰器模式 | 青训营笔记

94 阅读3分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 7 天

1. 主要内容

概念介绍,解决的问题

代码演示和 UML 类图

应用场景 + AOP(面向切面编程)

1)学习方法

UML类图要结合代码理解

设计模式要结合使用场景,否则记不住

2)注意事项

Angular 只是为了演示,国内应用不多

AOP 可以先了解概念,不急于掌握细节和实践

2. 装饰器模式

针对一个对象

动态的添加新功能

但不改变它原有的功能

1)手机壳

我们手机就是一个手机,但装上了手机壳,那不是锦上添花了吗,更加防摔,更加美观

image-20230112101654420

2)UML 类图

image-20230112102125415

3)代码演示 - 基础版,可进阶

 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();

4)符合开放封闭原则

装饰器和目标分离,结构

装饰器可自由扩展

目标可自有扩展

5)总结

UML 类图

代码演示

符合开放封闭原则

3. 装饰器模式 - 场景

装饰 class

装饰 class method

Angular 中使用装饰器

React-redux 使用了装饰器思想

1)装饰 class - 代码演示

先在 tsconfig.jsoncompilerOptions 加入 experimentalDecorator 为 true

 {
   "compilerOptions": {
     ...,
     "experimentalDecorators": true
   },
   "include": ["src"]
 }

不加这句话,语法可能会报错,添加了仍然报错

参考文档:VSCode中"experimentalDecorators"设置无效 - 简书 (jianshu.com)

解决方法:

原因:那么就是你的 ts 文件,没有在 tsconfig.json 文件的管理范围内

解决:1. 把 ts 放在 src 目录下即可 2. // @ts-ignore 忽略

最简易的装饰器代码:

 // 装饰器函数
 function testable(target: any) {
   target.isTestable = true;
 }
 ​
 @testable
 // @ts-ignore
 class Foo {
   static isTestable?: boolean;
 }
 ​
 console.log(Foo.isTestable);

如果我需要通过参数来决定设置的值,使用工厂思想进行封装:

 // 装饰器的工厂函数(对比:工厂模式)
 function testable(val: boolean) {
   // 装饰器函数
   return function (target: any) {
     target.isTestable = val;
   };
 }
 ​
 @testable(false)
 // @ts-ignore
 class Foo {
   static isTestable?: boolean;
 }
 ​
 console.log(Foo.isTestable);

2)装饰 class method - 代码演示

 /**
  * readOnly 设置目标为只读
  * @param target 目标函数对象
  * @param key 目标函数名
  * @param decorator 对象属性描述符
  */
 function readOnly(target: any, key: string, descriptor: PropertyDescriptor) {
   descriptor.writable = false;
 }
 ​
 /**
  * configurable 修改目标的 configurable 为指定值
  * @param val 布尔
  * @returns 装饰器函数
  */
 function configurable(val: boolean) {
   return function (target: any, key: string, descriptor: PropertyDescriptor) {
     descriptor.configurable = val;
   };
 }
 ​
 class Foo {
   private name = "张三";
   private age = 20;
 ​
   @readOnly
   // @ts-ignore
   getName() {
     return this.name;
   }
 ​
   @configurable(false)
   // @ts-ignore
   getAge() {
     return this.age;
   }
 }
 ​
 const f = new Foo();
 // @ts-ignore
 // f.getName = () => "66";
 ​
 // @ts-ignore
 console.log(Object.getOwnPropertyDescriptor(f.__proto__, "getAge"));

3)总结

装饰 class 和 class method

装饰器就是一个函数,结合 ES 的 Decorator 语法

react-redux 和 Angular

4. AOP

Aspect Oriented Program 面向切面编程

业务和系统基础功能分离,和 Decorator 很配

AOP 和 OOP 并不冲突

1)图形理解

image-20230112115943592

我们先把基础的日志、安装和其他功能写好,然后在业务功能发布时,切入一个日志功能,实现业务功能和系统功能相分离

2)代码演示 - 进阶版

 /**
  * log 记录日志
  * @param target 目标函数对象
  * @param key 目标函数名
  * @param decorator 对象属性描述符
  */
 function log(target: any, key: string, descriptor: PropertyDescriptor) {
   const oldVal = descriptor.value;
 ​
   // 重新定义 fn1 方法
   descriptor.value = function () {
     console.log("日志功能");
     return oldVal.apply(this, arguments);
   };
 }
 ​
 class Foo {
   @log
   // @ts-ignore
   fn1() {
     console.log("业务功能");
   }
 }
 ​
 Foo.prototype.fn1();
 ​

3)使用 AOP 和不使用 AOP 思想

image-20230112121252420