使用装饰者模式作有趣的事情

什么是装饰者模式

装饰者模式是一种为函数或类增添特性的技术,它可让咱们在不修改原来对象的基础上,为其增添新的能力和行为。它本质上也是一个函数(在javascipt中,类也只是函数的语法糖)。javascript

咱们何时能够弄到它呢

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

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

自行车的基类以下:git

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

那么咱们能够先建立一个装饰者模式基类es6

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

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

有了这个基类以后,咱们就能够根据咱们的需求对原来的Bicycle类随心所欲了。好比我能够建立一个添加了前灯的装饰器以及添加了尾灯的装饰器:redux

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
bicycle = new HeadLightDecorator(bicycle); // 添加了前灯的自行车
console.log(bicycle.getPrice());  // 220
bicycle = new TailLightDecorator(bicycle); // 添加了前灯和尾灯的自行车
console.log(bicycle.getPrice()); // 240

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

从例子中咱们能够看出装饰者模式的适用场合:babel

  1. 若是你须要为类增添特性或职责,但是从类派生子类的解决方法并不太现实的状况下,就应该使用装饰者模式。
  2. 在例子中,咱们并无对原来的Bicycle基类进行修改,所以也不会对原有的代码产生反作用。咱们只是在原有的基础上增添了一些功能。所以,若是想为对象增添特性又不想改变使用该对象的代码的话,则能够采用装饰者模式。

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

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

作一些有趣的事情

既然知道了装饰者模式能够在不修改原来代码的状况下为其增添一些新的功能,那么咱们就能够来作一些有趣的事情。

咱们能够为一个类的方法提供性能分析的功能。

class TimeProfileDecorator {
  constructor(component, keys) {
    this.component = component;
    this.timers = {};
    const self = this;
    for (let i in keys) {
      let key = keys[i];
        if (typeof component[key] === 'function') {
          this[key] = function(...args) {
            this.startTimer(key);
            // 解决this引用错误问题
            component[key].call(component, ...args);
            this.logTimer(key);
          }
        }
    }
  }
  startTimer(namespace) {
    this.timers[namespace] = new Date();
  }
  logTimer(namespace) {
    const elapserdTime = (new Date()).getTime() - this.timers[namespace].getTime();
    console.log(`该函数消耗了${elapserdTime}ms`);
  }
}
// example
class Test {
  constructor() {
    this.name = 'cjg';
    this.age = 22;
  }
  sayName() {
    console.log(this.name);
  }
  sayAge() {
    console.log(this.age);
  }
}

let test1 = new Test();
test1 = new TimeProfileDecorator(test1, ['sayName', 'sayAge']);
console.log(test1.sayName());
console.log(test1.sayAge());

对函数进行加强

节流函数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);
    }
}

缓存函数返回值

// 缓存函数结果,对于一些计算量比较大的函数效果比较明显。
function memorize(func) {
    const cache = {};
    return function (...args) {
        const key = JSON.stringify(args);
        if (cache[key]) {
          console.log('缓存了');
          return cache[key];
        }
        const result = func.call(this, ...args);
        cache[key] = result;
        return result;
    };
}

function fib(num) {
  return num < 2 ? num : fib(num - 1) + fib(num - 2);
}

const enhanceFib = memorize(fib);
console.log(enhanceFib(40));
console.log(enhanceFib(40));
console.log(enhanceFib(40));
console.log(enhanceFib(40));

构造React高阶组件,为组件增长额外的功能,好比为组件提供shallowCompare功能:

import React from 'react';
const { Component } = react;

const ShadowCompareDecorator = (Instance) => class extends Component {
  shouldComponentUpdate(nextProps, nextState) {
    return !shallowCompare(this.props, nextProps) ||
      !shallowCompare(this.state, nextState);
  }
  render() {
    return (
      <Instance {...this.props} />
    );
  }
};

export default ShadowCompareDecorator;

固然,你若是用过react-redux的话,你确定也用过connect。其实connect也是一种高阶组件的方式。它经过装饰者模式,从Provider的context里拿到全局的state,而且将其经过props的方式传给原来的组件。

总结

使用装饰者模式可让咱们为原有的类和函数增添新的功能,而且不会修改原有的代码或者改变其调用方式,所以不会对原有的系统带来反作用。咱们也不用担忧原来系统会由于它而失灵或者不兼容。就我我的而言,我以为这是一种特别好用的设计模式。

一个好消息就是,js的装饰器已经加入了es7的草案里啦。它让咱们能够更加优雅的使用装饰者模式,若是有兴趣的能够添加下babel的plugins插件提早体验下。阮一峰老师的这个教程也十分浅显易懂。

参考文献:

Javascript设计模式

本文地址在->本人博客地址, 欢迎给个 start 或 follow

相关文章
相关标签/搜索