如果想要增强对象的功能,不妨给它加个装饰吧~
基本介绍
TypeScript 中已有内置的装饰者模式实现,但是这里还是需要介绍下装饰者模式详细的类结构设计。
先来看看装饰者模式的经典实现(也可以直接跳到下面的案例分析部分)
不过这里的 UML 只是简略的告诉我们类的设计范式,详细内容我们还是看下代码:
我们期望的使用的方式大概是这样:
const comp = new ConcreteComponent();
const decoratorA = new ConcreteDecoratorA();
const decoratorB = new ConcreteDecoratorB();
decoratorA.decorate(comp); // 使用 decoratorA 装饰一层
decoratorB.decorate(decoratorA); // 使用 decoratorB 再装饰一层
decoratorB.operation();
// default operation
// 装饰类 A 的具体行为
// 装饰类 B 的具体行为
abstract class Component {
abstract operation();
}
// 具体要被装饰的类
class ConcreteComponent extends Component {
operation() {
console.log(`default operation`);
}
}
// 抽象装饰类
abstract class Decorator extends Component {
protected comp: ComponentNode;
decorate(comp: ComponentNode) {
this.comp = comp;
}
operation() {
this.comp.operation();
}
decorateBehavior() {}
}
// 具体的装饰者
class ConcreteDecoratorA extends Decorator {
operation() {
super.operation();
this.addBehavior();
}
addBehavior() {
console.log(`装饰类 A 的具体行为`);
}
}
class ConcreteDecoratorB extends Decorator {
operation() {
super.operation();
this.addBehavior();
}
addBehavior() {
console.log(`装饰类 B 的具体行为`);
}
}
案例分析
假设在某些接口中,我们希望可以监测返回值是否包含 message 字段,如果是不包含则打印异常
function getResult(): HttpResult {
return this.http.get(`url`);
}
初版代码:
// 验证是否包含 message 字段
function isValid(value): boolean {
if(value && value['message'] ) return true;
else return false;
}
function getResult(): HttpResult {
const ret = this.http.get(`url`);
if(!isValid(ret)) console.warn('result 未包含 message 字段!');
return ret;
}
这样的代码有一个很严重的问题就是侵入性太强,其实判断是否有 message 字段与发送请求是松耦合的,我们不应该将 isValid 函数直接写入到 getResult 函数体中,那再来看看改进版的代码吧~
改进版:
// 验证是否包含 message 字段
function isValid(value): boolean {
if(value && value['message'] ) return true;
else return false;
}
function oldGetResult(): HttpResult {
return this.http.get(`url`);
}
function isValidDecorator(ret: HttpResult): HttpResult {
if(!isValid(ret)) console.warn('result 未包含 message 字段!');
return ret;
}
function getResult(): HttpResult {
return isValidDecorator(oldGetResult());
}
这样看起来要比之前好很多,不过 TypeScript 为我们提供了语言内置的装饰器功能,我们来看看使用 TS 的装饰器写起来会是怎样。
装饰器版:
不过我们需要先将这个函数包裹在类中作为一个方法来使用
class ResultService {
getResult(): HttpResult {
return this.http.get(`url`);
}
}
再编写我们的装饰器:
function isValid(value): boolean {
return value && value['message'] ? true : false;
}
export function isValidDecorator(
target: Object,
propertyName: string,
propertyDescriptor: PropertyDescriptor
): PropertyDescriptor {
const method = propertyDescriptor.value;
propertyDescriptor.value = function(...args: any[]) {
const result = method.apply(this, args);
// 原有的验证方法
if (!isValid(result)) {
console.warn('result 未包含 message 字段!');
}
// 返回调用函数的结果
return result;
};
return propertyDescriptor;
}
如果想要对 TypeScript 的装饰器做更深的了解,可以看看这里的中文文档
使用时非常的方便
class HttpReq {
// 只需添加这一行代码
@isValidDecorator
getResult() {
return this.http.get(`url`);
}
}
总结:
- 想要优雅的增强对象的功能,装饰者模式是个不错的选择
- TypeScript 中也已内置装饰器
欢迎阅读 TypeScript 设计模式与重构技巧 系列的其它文章:
Wave9:TypeScript 设计模式与重构技巧 · 开篇zhuanlan.zhihu.com