什么是裝飾者模式
裝飾者模式是一種為函數(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)合:
- 如果你需要為類增添特性或職責(zé),可是從類派生子類的解決方法并不太現(xiàn)實(shí)的情況下润绵,就應(yīng)該使用裝飾者模式线椰。
- 在例子中,我們并沒(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ì)模式