装饰器模式

1,363 阅读4分钟

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

2. 参考来源

  1. 菜鸟教程-装饰器模式
  2. 博客园-陈陈jg