1. 概述
装饰器模式是一种可以为函数或类增添特性的代码实现方式。
可以在不修改原有对象的基础上,为其增添新的属性和方法。
网上有一个很好的例子解释为什么需要装饰器模式。假设有一个自行车商行,它要求店内的自行车根据不同的配件售卖不同的价格,配置项包括前灯,尾灯,铃铛,修理扳手等,可以选择一种或者几种自由组合。
试想一下组合结果有几十上百中,每增加一种配件组合方式都是恐怖的。如果用类来实现意味着要为每一种组合定义一个类型。这明显不科学。
针对这种情况就可以使用装饰器来解决这个问题。装饰器类似于组合,针对每一个配件定义一个类,比如无配件的自行车是一个类,前灯是一个类,尾灯是一个类。这三个组合到一起就是一台具有前灯和尾灯的类。如果只需要尾灯配件就将自行车基类和尾灯类组合。
所以这里需要两个类对象,一个是自行车的基类Bicycle
就是具备自行车基本功能的类,第二个是用不组合的装饰器类BicycleDecotator
自行车基类中包含基本的ride
方法,以及基本售价也就是没有任何配件的售价。
// 自行车基类
class Bicycle {
// 其它方法
ride () {
console.log('骑行方法');
}
getPrice() {
// 售价100
return 100;
}
}
创建一个装饰者模式基类,这个基类其实没有做什么事情,他的功能只是接受一个Bicycle
实例,实现其对应的方法,并且将调用其方法返回而已。
class BicycleDecotator {
constructor(bicycle) {
this.bicycle = bicycle;
}
ride () {
return this.bicycle.ride();
}
getPrice() {
return this.bicycle.getPrice();
}
}
有了这个基类之后,就可以根据需求对原来的Bicycle
类为所欲为了。比如我可以创建一个添加了前灯的装饰器。这个装饰器在原本的价格上增加10
元。
class HeadLightDecorator extends BicycleDecorator {
constructor(bicycle) {
super(bicycle);
}
getPrice() {
return this.bicycle.getPrice() + 10;
}
}
还可以创建一个添加了尾灯的装饰器,同样价格上增加10
元。
class TailLightDecorator extends BicycleDecorator {
constructor(bicycle) {
super(bicycle);
}
getPrice() {
return this.bicycle.getPrice() + 20;
}
}
接下来就可以来对其自由组合了,bicycle
实例是基本款自行车,售价10
元。
let bicycle = new Bicycle(); // 原始自行车
console.log(bicycle.getPrice()); // 100
将基本款和前灯进行组合,就是创建HeadLightDecorator
实例,传入bicycle
实例。
const headbicycle = new HeadLightDecorator(bicycle);
console.log(headbicycle.getPrice()); // 110
将添加前灯的自行车和尾灯组合就是前灯和尾灯都具备的自行车。
const tailbicycle = new TailLightDecorator(headbicycle);
console.log(tailbicycle.getPrice()); // 120
这样写的好处是假设说有10
个配件,那么只需要写10
个配件装饰器,然后就可以任意搭配成不同配件的自行车并计算价格。而如果是按照子类的实现方式的话,10
个配件可能就需要有几百个甚至上千个子类了, 听上去都很恐怖。
装饰者模式的使用场合:
如果你需要为类增添特性或职责,可是从类派生子类的解决方法并不太现实的情况下,就应该使用装饰者模式。
在例子中,并没有对原来的Bicycle
基类进行修改,因此也不会对原有的代码产生副作用。只是在原有的基础上增添了一些功能。因此,如果想为对象增添特性又不想改变使用该对象的代码的话,则可以采用装饰者模式。
或许你早就已经在用这种方式了,只是你并不知道这就是装饰器。前端常用的节流和防抖其实即使装饰器模式,只是在js
中习惯叫他高阶函数而已。
function throttle(func, delay) {
const self = this;
let tid;
return function(...args) {
if (tid) return;
tid = setTimeout(() => {
func.call(self, ...args);
tid = null;
}, delay);
}
}
function debounce(func, delay) {
const self = this;
let tid;
return function(...args) {
if (tid) clearTimeout(tid);
tid = setTimeout(() => {
func.call(self, ...args);
tid = null;
}, delay);
}
}