装饰者模式是一种为函数或类增添特性的技术,它可以让我们在不修改原来对象的基础上,为其增添新的能力和行为。它本质上也是一个函数。

我们来假设一个场景,一个自行车商店有几种型号的自行车,现在商店允许用户为每一种自行车提供一些额外的配件,比如前灯、尾灯、铃铛等。每选择一种或几种配件都会影响自行车的售价。

如果按照比较传统的创建子类的方式,就等于我们目前有一个自行车基类,而我们要为每一种可能的选择创建一个新的类。可是由于用户可以选择一种或者几种任意的配件,这就导致最终可能会生产几十上百个子类,这明显是不科学的。然而,对这种情况,我们可以使用装饰者模式来解决这个问题。

class Bicycle {
    // 其它方法
    wash () {}
    ride () {}
    getPrice() {
        return 200;
    }
}

我们可以先创建一个装饰者模式基类

class BicycleDecotator {
    constructor(bicycle) {
        this.bicycle = bicycle;
    }
    wash () {
        return this.bicycle.wash();
    }
    ride () {
        return this.bicycle.ride();
    }
    getPrice() {
        return this.bicycle.getPrice();
    }
}

这个基类其实没有做什么事情,它只是接受一个Bicycle实例,实现其对应的方法,并且将调用其方法返回而已。

有了这个基类之后,我们就可以根据我们的需求对原来的Bicycle类为所欲为了。比如我可以创建一个添加了前灯的装饰器以及添加了尾灯的装饰器:

class HeadLightDecorator extends BicycleDecorator {
    constructor(bicycle) {
        super(bicycle);
    }
    getPrice() {
        return this.bicycle.getPrice() + 20;
    }
}
class TailLightDecorator extends BicycleDecorator {
    constructor(bicycle) {
        super(bicycle);
    }
    getPrice() {
        return this.bicycle.getPrice() + 20;
    }
}

那么,接下来我们就可以来对其自由组合了:

let bicycle = new Bicycle(); // 原始自行车
console.log(bicycle.getPrice()); // 200

const bicycle1 = new HeadLightDecorator(bicycle); //原始自行车进行包装 添加了前灯的自行车
console.log(bicycle.getPrice());  // 220

const bicycle2 = new TailLightDecorator(bicycle); // 添加了前灯的自行车编程添加了前灯和尾灯的自行车
console.log(bicycle.getPrice()); // 240

这样写的好处是什么呢?假设说我们有10个配件,那么我们只需要写10个配件装饰器,然后就可以任意搭配成不同配件的自行车并计算价格。而如果是按照子类的实现方式的话,10个配件可能就需要有几百个甚至上千个子类了。

从例子中我们可以看出装饰者模式的适用场合:

或许你早就已经在用这种方式了,只是你并不知道这就是装饰器。

装饰者模式除了可以应用在类上之外,还可以应用在函数上(其实这就是高阶函数)。比如,我们想测量函数的执行时间,那么我可以写这么一个装饰器:

function func() {
    console.log('func');
}
function timeProfileDecorator(func) {
    return function (...args) {
        const startTime = new Date();
        func.call(this, ...args);
        const elapserdTime = (new Date()).getTime() - startTime.getTime();
        console.log(`该函数消耗了${elapserdTime}ms`);
    }
}
const newFunc = timeProfileDecorator(func);
console.log(newFunc());

既然知道了装饰者模式可以在不修改原来代码的情况下为其增添一些新的功能,那么我们就可以来做一些有趣的事情,节流函数or防抖函数。

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

使用装饰者模式可以让我们为原有的类和函数增添新的功能,并且不会修改原有的代码或者改变其调用方式,因此不会对原有的系统带来副作用。我们也不用担心原来系统会因为它而失灵或者不兼容。就我个人而言,我觉得这是一种特别好用的设计模式。ES7已经加入了装饰器模式,大家可以自行查阅。