前言
傳送地址:
設(shè)計模式第四篇,來一個與之前代理模式容易混淆的裝飾模式(本來打算寫觀察者模式,此處再次實名 diss 某人的埋點博客寫的速度)
裝飾器模式
什么是裝飾模式?
裝飾(Decorator)模式的定義:指在不改變現(xiàn)有對象結(jié)構(gòu)的情況下,動態(tài)地給該對象增加一些職責(zé)(即增加其額外功能)的模式,它屬于對象結(jié)構(gòu)型模式。此處引用了 gof 設(shè)計模式的描述。
裝飾(Decorator)模式的主要優(yōu)點有:
- 采用裝飾模式擴(kuò)展對象的功能比采用繼承方式更加靈活。
- 可以設(shè)計出多個不同的具體裝飾類,創(chuàng)造出多個不同行為的組合。
其主要缺點是:裝飾模式增加了許多子類,如果過度使用會使程序變得很復(fù)雜。
裝飾模式跟代理模式的區(qū)分
首先兩者都是對方法的補(bǔ)充,故而在功能上是有重疊的部分,所以在看代理模式的時候會存在一些疑惑,這倆功能不是差不多的嗎?這里簡單區(qū)分一下二者的特點
代理模式重在于對方法的控制,添加行為對于用戶是被動的;
裝飾模式重在于裝飾方法,增加方法的功能,添加裝飾對于用戶是主動的。
也就是說,代理模式在使用的時候,被代理的對象不會被修改,功能是單一且已知,對代理的過程無感,被代理的對象既不知道代理過程做了什么,也不會因為代理而改變他自己本身的屬性。
而裝飾模式是在裝飾源對象的過程中就拓展了功能與屬性,源對象本身的屬性就已經(jīng)被執(zhí)行的時候就已經(jīng)被動態(tài)的修改了。
兩者對于開發(fā)者最大的區(qū)別來說,裝飾模式是開發(fā)者主動去修改源對象,代理模式是去被動的拓展源對象的功能。
裝飾模式跟代理模式通俗理解
好的,回歸到我們的初心-開寵物店的栗子中
我們在代理模式中提到過,我們可以選擇代理、加盟的模式去將我們的生意擴(kuò)大開來。但是隨之而來有一定的問題,作為代理商,他們的選擇可以多樣化,我們的基礎(chǔ)服務(wù)之外,他們會將自身的經(jīng)營想法跟管理理念融入進(jìn)去,除了貨物源頭之外,我們實際上沒辦法對代理商進(jìn)行很高的約束。這樣的話,代理商可能會在賣寵物的時候,搭配一些比較辣雞的服務(wù)(過期寵物糧食、劣質(zhì)的寵物牢籠等)來影響我們自己自身的品牌效應(yīng)。
既然有這個情況出現(xiàn),那我們可以加入直營店的概念或者在我們提供基礎(chǔ)服務(wù)的時候,主動的將拓展服務(wù)提供出來,從而維護(hù)我們寵物店的品牌與影響力。
項目實戰(zhàn)
業(yè)務(wù)發(fā)送 ajax 請求
const method = (type, url) => {
switch (type) {
case 'GET': {
return (target, name, descriptor) => {
return {
...descriptor,
value(query) {
fetch.get({
url,
query
}).then(data => {
descriptor.value.apply({ result: data }, [...arguments])
}).catch(err => {
console.log('err====>', err)
})
}
}
}
}
default: {
return (target, name, descriptor) => {
}
}
}
}
class Business {
@method('GET', 'https://api.github.com/users/octocat')
getOct(params) {
console.log(params)
console.log('result==>', this.result)
}
}
const business = new Business()
business.getOct({
test: 1
})
上述是將業(yè)務(wù)方法使用裝飾模式優(yōu)雅的包了一層,實際項目中如果有簡單的業(yè)務(wù)請求,這么使用可以優(yōu)雅一點,跟上一篇 fetch 實戰(zhàn)項目篇最后介紹業(yè)務(wù)請求封裝不太一樣,這種是將請求直接寫在了業(yè)務(wù)方法當(dāng)中,優(yōu)點是簡潔明了,可以迅速了解到此業(yè)務(wù)的請求 url 跟方式,缺陷是多重嵌套請求不好處理
添加埋點
function consuming(target, name, descriptor) {
return {
...descriptor,
value(query) {
try {
console.time('consuming')
console.log('發(fā)送埋點', query)
descriptor.value.apply(this, [...arguments])
} finally {
console.timeEnd('consuming')
}
}
}
}
class Business {
@consuming
@method('GET', 'https://api.github.com/users/octocat')
getOct(params) {
console.log(params)
console.log('result==>', this.result)
}
}
裝飾模式可以自由的將多種裝飾器組合使用,類似于洋蔥模式,所以我們可以將埋點功能使用裝飾器添加在方法中。
函數(shù)-防抖與節(jié)流
眾所周知,防抖函數(shù)在日常開發(fā)中還是比較常見的一種需求,so 我們也可以將防抖函數(shù)已裝飾器模式靈活的添加進(jìn)去
function debounce(wait, immediate) { // 此處防抖函數(shù),可以延遲跟立即執(zhí)行兩種
let timer;
return (target, name, descriptor) => {
return {
...descriptor,
value(query) {
if (timer) clearTimeout(timer);
if (immediate) {
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) descriptor.value.apply(this, [...arguments]);
} else {
timer = setTimeout(() => {
descriptor.value.apply(this, [...arguments])
}, wait)
}
}
}
}
}
class Business {
@debounce(3000)
@consuming
@method('GET', 'https://api.github.com/users/octocat')
getOct(params) {
console.log(params)
console.log('result==>', this.result)
}
}
既然防抖可以,那么節(jié)流亦可
function throttle(wait) {
let timeout;
return (target, name, descriptor) => {
return {
...descriptor,
value(query) {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
descriptor.value.apply(this, [...arguments])
}, wait)
}
}
}
}
}
以上是裝飾模式在項目中的實例使用,部分可以直接使用,部分是作為技術(shù)探討。
尾聲
完整的 demo 地址:項目實戰(zhàn) demo,喜歡的朋友可以 star 一下,后續(xù)會根據(jù)設(shè)計模式博文的推出,逐步的將此項目繼續(xù)拓展出來。