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

什么是裝飾者模式

裝飾者模式是一種為函數(shù)或類增添特性的技術(shù)晨仑,它可以讓我們?cè)诓恍薷脑瓉?lái)對(duì)象的基礎(chǔ)上技健,為其增添新的能力和行為翩蘸。它本質(zhì)上也是一個(gè)函數(shù)(在javascipt中币喧,類也只是函數(shù)的語(yǔ)法糖)。

我們什么時(shí)候可以弄到它呢

我們來(lái)假設(shè)一個(gè)場(chǎng)景伤为,一個(gè)自行車商店有幾種型號(hào)的自行車咒循,現(xiàn)在商店允許用戶為每一種自行車提供一些額外的配件据途,比如前燈、尾燈叙甸、鈴鐺等颖医。每選擇一種或幾種配件都會(huì)影響自行車的售價(jià)。

如果按照比較傳統(tǒng)的創(chuàng)建子類的方式裆蒸,就等于我們目前有一個(gè)自行車基類熔萧,而我們要為每一種可能的選擇創(chuàng)建一個(gè)新的類×诺唬可是由于用戶可以選擇一種或者幾種任意的配件佛致,這就導(dǎo)致最終可能會(huì)生產(chǎn)幾十上百個(gè)子類,這明顯是不科學(xué)的辙谜。然而俺榆,對(duì)這種情況,我們可以使用裝飾者模式來(lái)解決這個(gè)問(wèn)題装哆。

自行車的基類如下:

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

那么我們可以先創(chuàng)建一個(gè)裝飾者模式基類

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

這個(gè)基類其實(shí)沒(méi)有做什么事情罐脊,它只是接受一個(gè)Bicycle實(shí)例,實(shí)現(xiàn)其對(duì)應(yīng)的方法蜕琴,并且將調(diào)用其方法返回而已萍桌。

有了這個(gè)基類之后,我們就可以根據(jù)我們的需求對(duì)原來(lái)的Bicycle類為所欲為了奸绷。比如我可以創(chuàng)建一個(gè)添加了前燈的裝飾器以及添加了尾燈的裝飾器:

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

那么梗夸,接下來(lái)我們就可以來(lái)對(duì)其自由組合了:

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

這樣寫的好處是什么呢?假設(shè)說(shuō)我們有10個(gè)配件层玲,那么我們只需要寫10個(gè)配件裝飾器号醉,然后就可以任意搭配成不同配件的自行車并計(jì)算價(jià)格。而如果是按照子類的實(shí)現(xiàn)方式的話辛块,10個(gè)配件可能就需要有幾百個(gè)甚至上千個(gè)子類了畔派。

從例子中我們可以看出裝飾者模式的適用場(chǎng)合:

  1. 如果你需要為類增添特性或職責(zé),可是從類派生子類的解決方法并不太現(xiàn)實(shí)的情況下润绵,就應(yīng)該使用裝飾者模式线椰。
  2. 在例子中,我們并沒(méi)有對(duì)原來(lái)的Bicycle基類進(jìn)行修改尘盼,因此也不會(huì)對(duì)原有的代碼產(chǎn)生副作用憨愉。我們只是在原有的基礎(chǔ)上增添了一些功能。因此卿捎,如果想為對(duì)象增添特性又不想改變使用該對(duì)象的代碼的話配紫,則可以采用裝飾者模式。

裝飾者模式除了可以應(yīng)用在類上之外午阵,還可以應(yīng)用在函數(shù)上(其實(shí)這就是高階函數(shù))躺孝。比如,我們想測(cè)量函數(shù)的執(zhí)行時(shí)間,那么我可以寫這么一個(gè)裝飾器:

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(`該函數(shù)消耗了${elapserdTime}ms`);
    }
}
const newFunc = timeProfileDecorator(func);
console.log(newFunc());

做一些有趣的事情

既然知道了裝飾者模式可以在不修改原來(lái)代碼的情況下為其增添一些新的功能植袍,那么我們就可以來(lái)做一些有趣的事情惧眠。

我們可以為一個(gè)類的方法提供性能分析的功能。

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引用錯(cuò)誤問(wèn)題
            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(`該函數(shù)消耗了${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());

對(duì)函數(shù)進(jìn)行增強(qiáng)

節(jié)流函數(shù)or防抖函數(shù)

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

緩存函數(shù)返回值

// 緩存函數(shù)結(jié)果于个,對(duì)于一些計(jì)算量比較大的函數(shù)效果比較明顯氛魁。
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));

構(gòu)造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;

當(dāng)然览濒,你如果用過(guò)react-redux的話呆盖,你肯定也用過(guò)connect。其實(shí)connect也是一種高階組件的方式贷笛。它通過(guò)裝飾者模式应又,從Provider的context里拿到全局的state,并且將其通過(guò)props的方式傳給原來(lái)的組件乏苦。

總結(jié)

使用裝飾者模式可以讓我們?yōu)樵械念惡秃瘮?shù)增添新的功能株扛,并且不會(huì)修改原有的代碼或者改變其調(diào)用方式,因此不會(huì)對(duì)原有的系統(tǒng)帶來(lái)副作用汇荐。我們也不用擔(dān)心原來(lái)系統(tǒng)會(huì)因?yàn)樗ъ`或者不兼容洞就。就我個(gè)人而言,我覺(jué)得這是一種特別好用的設(shè)計(jì)模式掀淘。

一個(gè)好消息就是旬蟋,js的裝飾器已經(jīng)加入了es7的草案里啦。它讓我們可以更加優(yōu)雅的使用裝飾者模式革娄,如果有興趣的可以添加下babel的plugins插件提前體驗(yàn)下倾贰。阮一峰老師的這個(gè)教程也十分淺顯易懂。

參考文獻(xiàn):

Javascript設(shè)計(jì)模式

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末拦惋,一起剝皮案震驚了整個(gè)濱河市匆浙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌厕妖,老刑警劉巖首尼,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異言秸,居然都是意外死亡软能,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門举畸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)查排,“玉大人,你說(shuō)我怎么就攤上這事俱恶”⑧拢” “怎么了范舀?”我有些...
    開(kāi)封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)了罪。 經(jīng)常有香客問(wèn)我锭环,道長(zhǎng),這世上最難降的妖魔是什么泊藕? 我笑而不...
    開(kāi)封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任辅辩,我火速辦了婚禮,結(jié)果婚禮上娃圆,老公的妹妹穿的比我還像新娘玫锋。我一直安慰自己,他們只是感情好讼呢,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布撩鹿。 她就那樣靜靜地躺著,像睡著了一般悦屏。 火紅的嫁衣襯著肌膚如雪节沦。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天础爬,我揣著相機(jī)與錄音甫贯,去河邊找鬼。 笑死看蚜,一個(gè)胖子當(dāng)著我的面吹牛叫搁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播供炎,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼渴逻,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了碱茁?” 一聲冷哼從身側(cè)響起裸卫,我...
    開(kāi)封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤仿贬,失蹤者是張志新(化名)和其女友劉穎纽竣,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體茧泪,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜓氨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了队伟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片穴吹。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖嗜侮,靈堂內(nèi)的尸體忽然破棺而出港令,到底是詐尸還是另有隱情啥容,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布顷霹,位于F島的核電站咪惠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏淋淀。R本人自食惡果不足惜遥昧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望朵纷。 院中可真熱鬧炭臭,春花似錦、人聲如沸袍辞。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)搅吁。三九已至凿试,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間似芝,已是汗流浹背那婉。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留党瓮,地道東北人详炬。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像寞奸,于是被迫代替她去往敵國(guó)和親呛谜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • 設(shè)計(jì)模式概述 在學(xué)習(xí)面向?qū)ο笃叽笤O(shè)計(jì)原則時(shí)需要注意以下幾點(diǎn):a) 高內(nèi)聚枪萄、低耦合和單一職能的“沖突”實(shí)際上隐岛,這兩者...
    彥幀閱讀 3,736評(píng)論 0 14
  • 參考資料:菜鳥(niǎo)教程之設(shè)計(jì)模式 設(shè)計(jì)模式概述 設(shè)計(jì)模式(Design pattern)代表了最佳的實(shí)踐,通常被有經(jīng)驗(yàn)...
    Steven1997閱讀 1,170評(píng)論 1 12
  • 如圖↑“樹(shù)莓“瓷翻,昨天 我們幾個(gè)小伙伴都沒(méi)吃過(guò)所以買來(lái)嘗鮮聚凹,遇到雯子 請(qǐng)她吃 她說(shuō)這是什么呀?我認(rèn)真臉說(shuō) 我們新疆的...
    梵音陳靜閱讀 409評(píng)論 0 0
  • 沒(méi)做到的地方齐帚,看是否能盡量做到妒牙;做到的地方,是否能做到更好对妄。 成長(zhǎng)率就是指一個(gè)人除了不斷成長(zhǎng)外湘今,成長(zhǎng)的速度還越來(lái)越...
    rusty6kimo閱讀 60評(píng)論 0 0
  • 我只是需要一個(gè)二維碼,正文待編輯剪菱。
    美少女壯士暮年閱讀 102評(píng)論 0 0