使用裝飾者模式作有趣的事情

什麼是裝飾者模式

裝飾者模式是一種爲函數或類增添特性的技術,它可讓咱們在不修改原來對象的基礎上,爲其增添新的能力和行爲。它本質上也是一個函數(在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

相關文章
相關標籤/搜索