装饰者模式
首发于:2022-03-27
基本概念
装饰者模式 (Decorator Pattern)又称装饰器模式,在不改变原对象的基础上,通过对其添加属性或方法来进行包装拓展,使得原有对象可以动态具有更多功能。
本质是功能动态组合,即动态地给一个对象添加额外的职责,就增加功能角度来看,使用装饰者模式比用继承更为灵活。好处是有效地把对象的核心职责和装饰功能区分开,并且通过动态增删装饰去除目标对象中重复的装饰逻辑。
现实生活中的例子
最典型的例子就是房屋装修了吧,比如:你买了一间毛坯房,你住进去之前肯定会装修一番吧,刷墙、铺地、通水电、安装家具、安装电器等等,这就让房屋更美观,有了你自己风格,也有了更多的功能,住起来就会更舒适。不过装修这个动作并不会影响房屋的主体结构,也不会影响房屋是用来住的这个基本功能。
再比如:冲一杯咖啡,之后我们自己往里面加糖、加冰、加咖啡伴侣等的东西,这就让你的咖啡有更多的风味,但是这并不影响它还是一杯咖啡。
应用场景
TS 中的装饰器其实就是装饰者模式的一个典型应用,还可以给浏览器事件添加新功能。装饰者模式的应用场景一般有以下特点:
- 如果不希望系统中增加很多子类,那么可以考虑使用装饰者模式;
- 需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承关系很难实现,这时采用装饰者模式可以很好实现;
- 当对象的功能要求可以动态地添加,也可以动态地撤销,可以考虑使用装饰者模式;
优缺点
优点:
- 我们经常使用继承的方式来实现功能的扩展,但这样会给系统中带来很多的子类和复杂的继承关系,装饰者模式允许用户在不引起子类数量暴增的前提下动态地修饰对象,添加功能,装饰者和被装饰者之间松耦合,可维护性好;
- 被装饰者可以使用装饰者动态地增加和撤销功能,可以在运行时选择不同的装饰器,实现不同的功能,灵活性好;
- 装饰者模式把一系列复杂的功能分散到每个装饰器当中,一般一个装饰器只实现一个功能,可以给一个对象增加多个同样的装饰器,也可以把一个装饰器用来装饰不同的对象,有利于装饰器功能的复用;
- 可以通过选择不同的装饰者的组合,创造不同行为和功能的结合体,原有对象的代码无须改变,就可以使得原有对象的功能变得更强大和更多样化,符合开闭原则;
缺点:
- 使用装饰者模式时会产生很多细粒度的装饰者对象,这些装饰者对象由于接口和功能的多样化导致系统复杂度增加,功能越复杂,需要的细粒度对象越多;
- 由于更大的灵活性,也就更容易出错,特别是对于多级装饰的场景,错误定位会更加繁琐;
实现
TS 的装饰器的使用案例:
ts
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@enumerable(false)
get x() { return this._x; }
@enumerable(true)
get y() { return this._y; }
}
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(target, propertyKey, descriptor)
descriptor.enumerable = value;
};
}
const p = new Point(10, 20);
for (const key in p) {
console.log(key)
}
结果:
_x
_y
y
装饰器开发更多案例可以看这里-装饰器 · TypeScript中文网 · TypeScript——JavaScript的超集 (tslang.cn)
浏览器事件埋点的装饰器的例子:
js
window.onload = function() {
console.log('原先的 onload 事件')
}
/* 发送埋点信息 */
function sendUserOperation() {
console.log('埋点:用户当前行为路径为...')
}
/* 给原生事件添加新的装饰方法 */
function originDecorateFn(originObj, originKey, fn) {
originObj[originKey] = function() {
const originFn = originObj[originKey]
return function() {
originFn && originFn()
fn()
}
}()
}
// 添加装饰功能
originDecorateFn(window, 'onload', sendUserOperation)
结果
原先的 onload 事件
埋点:用户当前行为路径为...