这是我参与「第五届青训营」伴学笔记创作活动的第 7 天
1. 主要内容
概念介绍,解决的问题
代码演示和 UML 类图
应用场景 + AOP(面向切面编程)
1)学习方法
UML类图要结合代码理解
设计模式要结合使用场景,否则记不住
2)注意事项
Angular 只是为了演示,国内应用不多
AOP 可以先了解概念,不急于掌握细节和实践
2. 装饰器模式
针对一个对象
动态的添加新功能
但不改变它原有的功能
1)手机壳
我们手机就是一个手机,但装上了手机壳,那不是锦上添花了吗,更加防摔,更加美观
2)UML 类图
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.json 的 compilerOptions 加入 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)图形理解
我们先把基础的日志、安装和其他功能写好,然后在业务功能发布时,切入一个日志功能,实现业务功能和系统功能相分离
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();