日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
我經(jīng)常使用的三種有用的設(shè)計(jì)模式

什么是設(shè)計(jì)模式?我們?yōu)槭裁葱枰獙W(xué)習(xí)設(shè)計(jì)模式?

網(wǎng)上已經(jīng)有很多開發(fā)者在討論。我不知道你怎么想,但對我來說:設(shè)計(jì)模式是我個人覺得可以更好解決問題的一種方案。

這意味著什么?如果你開發(fā)的項(xiàng)目的功能是固定的,永遠(yuǎn)不會調(diào)整業(yè)務(wù),那么你就不需要使用設(shè)計(jì)模式等任何技巧。您只需要使用通常的方式編寫代碼并完成需求即可。

但是,我們的開發(fā)項(xiàng)目的需求是不斷變化的,這就需要我們經(jīng)常修改我們的代碼。也就是說,我們現(xiàn)在寫代碼的時(shí)候,需要為未來業(yè)務(wù)需求可能發(fā)生的變化做好準(zhǔn)備。

這時(shí),你會發(fā)現(xiàn)使用設(shè)計(jì)模式可以讓你的代碼更具可擴(kuò)展性。

經(jīng)典的設(shè)計(jì)模式有 23 種,但并不是每一種設(shè)計(jì)模式都被頻繁使用。在這里,我介紹我最常用和最實(shí)用的 3 種設(shè)計(jì)模式。

1、策略模式

假設(shè)您目前正在從事一個電子商務(wù)商店的項(xiàng)目。每個產(chǎn)品都有一個原價(jià),我們可以稱之為 originalPrice。但并非所有產(chǎn)品都以原價(jià)出售,我們可能會推出允許以折扣價(jià)出售商品的促銷活動。

商家可以在后臺為產(chǎn)品設(shè)置不同的狀態(tài)。然后實(shí)際售價(jià)將根據(jù)產(chǎn)品狀態(tài)和原價(jià)動態(tài)調(diào)整。

具體規(guī)則如下:

部分產(chǎn)品已預(yù)售。為鼓勵客戶預(yù)訂,我們將在原價(jià)基礎(chǔ)上享受 20% 的折扣。

部分產(chǎn)品處于正常促銷階段。如果原價(jià)低于或等于100,則以10%的折扣出售;如果原價(jià)高于 100,則減 10 美元。

有些產(chǎn)品沒有任何促銷活動。它們屬于默認(rèn)狀態(tài),以原價(jià)出售。

如果你需要寫一個getPrice函數(shù),你應(yīng)該怎么寫呢?

function getPrice(originalPrice, status){
// ...
return price
}

其實(shí),面對這樣的問題,如果不考慮任何設(shè)計(jì)模式,最直觀的寫法可能就是使用if-else通過多個判斷語句來計(jì)算價(jià)格。

有三種狀態(tài),所以我們可以快速編寫如下代碼:

function getPrice(originalPrice, status) {
if (status === 'pre-sale') {
return originalPrice * 0.8
}
if (status === 'promotion') {
if (origialPrice <= 100) {
return origialPrice * 0.9
} else {
return originalPrice - 20
}
}
if (status === 'default') {
return originalPrice
}
}

有三個條件;然后,我們寫三個 if 語句,這是非常直觀的代碼。

但是這段代碼并不友好。

首先,它違反了單一職責(zé)原則。主函數(shù) getPrice 做了太多的事情。這個函數(shù)不易閱讀,也容易出現(xiàn)bug。如果一個條件有bug,整個函數(shù)就會崩潰。同時(shí),這樣的代碼也不容易調(diào)試。

然后,這段代碼很難應(yīng)對變化。正如我在文章開頭所說的那樣,設(shè)計(jì)模式往往會在業(yè)務(wù)邏輯發(fā)生變化時(shí)表現(xiàn)出它的魅力。

假設(shè)我們的業(yè)務(wù)擴(kuò)大了,現(xiàn)在還有另一個折扣促銷:黑色星期五,折扣規(guī)則如下:

  • 價(jià)格低于或等于 100 美元的產(chǎn)品以 20% 的折扣出售。
  • 價(jià)格高于 100 美元但低于 200 美元的產(chǎn)品將減少 20 美元。
  • 價(jià)格高于或等于 200 美元的產(chǎn)品將減少 20 美元。

這時(shí)候怎么擴(kuò)展getPrice函數(shù)呢?

看起來我們必須在 getPrice 函數(shù)中添加一個條件。

function getPrice(originalPrice, status) {
if (status === 'pre-sale') {
return originalPrice * 0.8
}
if (status === 'promotion') {
if (origialPrice <= 100) {
return origialPrice * 0.9
} else {
return originalPrice - 20
}
}
if (status === 'black-friday') {
if (origialPrice >= 100 && originalPrice < 200) {
return origialPrice - 20
} else if (originalPrice >= 200) {
return originalPrice - 50
} else {
return originalPrice * 0.8
}
}
if(status === 'default'){
return originalPrice
}
}

每當(dāng)我們增加或減少折扣時(shí),我們都需要更改函數(shù)。這種做法違反了開閉原則。修改已有函數(shù)很容易出現(xiàn)新的錯誤,也會讓getPrice越來越臃腫。

那么我們?nèi)绾蝺?yōu)化這段代碼呢?

首先,我們可以拆分這個函數(shù)以使 getPrice 不那么臃腫。

function preSalePrice(origialPrice) {
return originalPrice * 0.8
}
function promotionPrice(origialPrice) {
if (origialPrice <= 100) {
return origialPrice * 0.9
} else {
return originalPrice - 20
}
}
function blackFridayPrice(origialPrice) {
if (origialPrice >= 100 && originalPrice < 200) {
return origialPrice - 20
} else if (originalPrice >= 200) {
return originalPrice - 50
} else {
return originalPrice * 0.8
}
}
function defaultPrice(origialPrice) {
return origialPrice
}
function getPrice(originalPrice, status) {
if (status === 'pre-sale') {
return preSalePrice(originalPrice)
}
if (status === 'promotion') {
return promotionPrice(originalPrice)
}
if (status === 'black-friday') {
return blackFridayPrice(originalPrice)
}
if(status === 'default'){
return defaultPrice(originalPrice)
}
}

經(jīng)過這次修改,雖然代碼行數(shù)增加了,但是可讀性有了明顯的提升。我們的main函數(shù)顯然沒有那么臃腫,寫單元測試也比較方便。

但是上面的改動并沒有解決根本的問題:我們的代碼還是充滿了if-else,當(dāng)我們增加或減少折扣規(guī)則的時(shí)候,我們?nèi)匀恍枰薷膅etPrice。

想一想,我們之前用了這么多if-else,目的是什么?

實(shí)際上,使用這些 if-else 的目的是為了對應(yīng)狀態(tài)和折扣策略。

我們可以發(fā)現(xiàn),這個邏輯本質(zhì)上是一種映射關(guān)系:產(chǎn)品狀態(tài)與折扣策略的映射關(guān)系。

我們可以使用映射而不是冗長的 if-else 來存儲映射。比如這樣:

let priceStrategies = {
'pre-sale': preSalePrice,
'promotion': promotionPrice,
'black-friday': blackFridayPrice,
'default': defaultPrice
}

我們將狀態(tài)與折扣策略結(jié)合起來。那么計(jì)算價(jià)格會很簡單:

function getPrice(originalPrice, status) {
return priceStrategies[status](originalPrice)
}

這時(shí)候如果需要增減折扣策略,不需要修改getPrice函數(shù),我們只需在priceStrategies對象中增減一個映射關(guān)系即可。

之前的代碼邏輯如下:

現(xiàn)在代碼邏輯:

這樣是不是更簡潔嗎?

其實(shí)這招就是策略模式,是不是很實(shí)用?我不會在這里談?wù)摬呗阅J降臒o聊定義。如果你想知道策略模式的官方定義,你可以自己谷歌一下。

如果您的函數(shù)具有以下特征:

判斷條件很多。

各個判斷條件下的代碼相互獨(dú)立。。

然后,你可以將每個判斷條件下的代碼封裝成一個獨(dú)立的函數(shù),接著,建立判斷條件和具體策略的映射關(guān)系,使用策略模式重構(gòu)你的代碼。

2、發(fā)布-訂閱模式

這是我們在項(xiàng)目中經(jīng)常使用的一種設(shè)計(jì)模式,也經(jīng)常出現(xiàn)在面試中。

現(xiàn)在,我們有一個天氣預(yù)報(bào)系統(tǒng):當(dāng)極端天氣發(fā)生時(shí),氣象站會發(fā)布天氣警報(bào)。建筑工地、船舶和游客將根據(jù)天氣數(shù)據(jù)調(diào)整他們的日程安排。

一旦氣象站發(fā)出天氣警報(bào),他們會做以下事情:

  • 建筑工地:停工
  • 船舶:停泊靠岸
  • 游客:取消行程

如果,我們被要求編寫可用于通知天氣警告的代碼,你會想怎么做?

編寫天氣警告函數(shù)的常用方法可能是這樣的:

function weatherWarning(){
buildingsite.stopwork()
ships.mooring()
tourists.canceltrip()
}

這是一種非常直觀的寫法,但是這種寫法有很多不好的地方:

  • 耦合度太高。建筑工地、船舶和游客本來應(yīng)該是分開的,但現(xiàn)在它們被置于相同的功能中。其中一個對象中的錯誤可能會導(dǎo)致其他對象無法工作。顯然,這是不合理的。
  • 違反開閉原則。如果有新的訂閱者加入,那么我們只能修改weatherWarning函數(shù)。

造成這種現(xiàn)象的原因是氣象站承擔(dān)了主動告知各單位的責(zé)任。這就要求氣象站必須了解每個需要了解天氣狀況的單位。

但仔細(xì)想想,其實(shí),從邏輯上講,建筑工地、船舶、游客都應(yīng)該依靠天氣預(yù)報(bào),他們應(yīng)該是積極的一方。

我們可以將依賴項(xiàng)更改為如下所示:

氣象站發(fā)布通知,然后觸發(fā)事件,建筑工地、船舶和游客訂閱該事件。

氣象站不需要關(guān)心哪些對象關(guān)注天氣預(yù)警,只需要直接觸發(fā)事件即可。然后需要了解天氣狀況的單位主動訂閱該事件。

這樣,氣象站與訂閱者解耦,訂閱者之間也解耦。如果有新的訂閱者,那么它只需要直接訂閱事件,而不需要修改現(xiàn)有的代碼。

當(dāng)然,為了完成這個發(fā)布-訂閱系統(tǒng),我們還需要實(shí)現(xiàn)一個事件訂閱和分發(fā)系統(tǒng)。

可以這樣寫:

const EventEmit = function() {
this.events = {};
this.on = function(name, cb) {
if (this.events[name]) {
this.events[name].push(cb);
} else {
this.events[name] = [cb];
}
};
this.trigger = function(name, ...arg) {
if (this.events[name]) {
this.events[name].forEach(eventListener => {
eventListener(...arg);
});
}
};
};

我們之前的代碼,重構(gòu)以后變成這樣:

let weatherEvent = new EventEmit()
weatherEvent.on('warning', function () {
// buildingsite.stopwork()
console.log('buildingsite.stopwork()')
})
weatherEvent.on('warning', function () {
// ships.mooring()
console.log('ships.mooring()')
})
weatherEvent.on('warning', function () {
// tourists.canceltrip()
console.log('tourists.canceltrip()')
})
weatherEvent.trigger('warning')

如果你的項(xiàng)目中存在多對一的依賴,并且每個模塊相對獨(dú)立,那么你可以考慮使用發(fā)布-訂閱模式來重構(gòu)你的代碼。

事實(shí)上,發(fā)布訂閱模式應(yīng)該是我們前端開發(fā)者最常用的設(shè)計(jì)模式。

element.addEventListener('click', function(){
//...
})
// this is also publish-subscribe pattern

3、代理模式

現(xiàn)在我們的頁面上有一個列表:


  • Jon

  • Jack

  • bytefish

  • Rock Lee

  • Bob

我們想給頁面添加一個效果:每當(dāng)用戶點(diǎn)擊列表中的每個項(xiàng)目時(shí),都會彈出一條消息:Hi, I'm ${name}

我們將如何實(shí)現(xiàn)此功能?

大致思路是給每個li元素添加一個點(diǎn)擊事件。







Proxy Pattern



  • Jon

  • Jack

  • bytefish

  • Rock Lee

  • Bob




這種方法可以滿足要求,但這樣做的缺點(diǎn)是性能開銷,因?yàn)槊總€ li 標(biāo)簽都綁定到一個事件。如果列表中有數(shù)千個元素,我們是否綁定了數(shù)千個事件?

如果我們仔細(xì)看這段代碼,可以發(fā)現(xiàn)當(dāng)前的邏輯關(guān)系如下:

每個 li 都有自己的事件處理機(jī)制。但是我們發(fā)現(xiàn)不管是哪個li,其實(shí)都是ul的成員。我們可以將li的事件委托給ul,讓ul成為這些 li 的事件代理。

這樣,我們只需要為這些 li 元素綁定一個事件。

let container = document.getElementById('container')
container.addEventListener('click', function (e) {
console.log(e)
if (e.target.nodeName === 'LI') {
e.preventDefault()
alert(`Hi, I'm ${e.target.innerText}`)
}
})

這實(shí)際上是代理模式。

代理模式是本體不直接出現(xiàn),而是讓代理解決問題。

在上述情況下,li 并沒有直接處理點(diǎn)擊事件,而是將其委托給 ul。

現(xiàn)實(shí)生活中,明星并不是直接出來談生意,而是交給他們的經(jīng)紀(jì)人,也就是明星的代理人。

代理模式的應(yīng)用非常廣泛,我們來看另一個使用它的案例。

假設(shè)我們現(xiàn)在有一個計(jì)算函數(shù),參數(shù)是字符串,計(jì)算比較耗時(shí)。同時(shí),這是一個純函數(shù)。如果參數(shù)相同,則函數(shù)的返回值將相同。

function compute(str) {    
// Suppose the calculation in the funtion is very time consuming
console.log('2000s have passed')
return 'a result'
}

現(xiàn)在需要給這個函數(shù)添加一個緩存函數(shù):每次計(jì)算后,存儲參數(shù)和對應(yīng)的結(jié)果。在接下來的計(jì)算中,會先從緩存中查詢計(jì)算結(jié)果。

你會怎么寫代碼?

當(dāng)然,你可以直接修改這個函數(shù)的功能。但這并不好,因?yàn)榫彺娌⒉皇沁@個功能的固有特性。如果將來您不需要緩存,那么,您將不得不再次修改此功能。

更好的解決方案是使用代理模式。

const proxyCompute = (function (fn){
// Create an object to store the results returned after each function execution.
const cache = Object.create(null);
// Returns the wrapped function
return function (str) {
// If the cache is not hit, the function will be executed
if ( !cache[str] ) {
let result = fn(str);
// Store the result of the function execution in the cache
cache[str] = result;
}
return cache[str]
}
})(compute)

這樣,我們可以在不修改原函數(shù)技術(shù)的情況下為其擴(kuò)展計(jì)算函數(shù)。

這就是代理模式,它允許我們在不改變原始對象本身的情況下添加額外的功能。

結(jié)論

這些是我在日常項(xiàng)目中使用的設(shè)計(jì)模式。設(shè)計(jì)模式不是無聊的概念,它們是使我們的代碼易于擴(kuò)展的技術(shù)解決方案。

最后,希望這些例子對你有用,感謝閱讀。


網(wǎng)站欄目:我經(jīng)常使用的三種有用的設(shè)計(jì)模式
網(wǎng)頁地址:http://www.dlmjj.cn/article/dpjddcs.html