- 今日推薦
-
- 農(nóng)特微商平臺(tái)「農(nóng)特微電商業(yè)主」
- 電商如何快速發(fā)展「怎么讓電商用戶代運(yùn)營(yíng)呢」
- 英國(guó)亞馬遜熱賣產(chǎn)品「跨境電商產(chǎn)品類目分類」
- 餐飲供應(yīng)鏈最重要的是哪些「供應(yīng)鏈的要點(diǎn)」
- 跨境電子商務(wù)如何選品,舉例「跨境電商選品的方法有幾個(gè)」
- 倒閉的女鞋名牌企業(yè)「折服天下女裝加盟」
- 4大直播電商平臺(tái)暗藏了哪些商機(jī)「直播帶貨后面的商機(jī)」
- 雙十一要買的東西「雙十一為什么買的人多」
- 什么是私域運(yùn)營(yíng)「用戶運(yùn)營(yíng)每天的工作」
- 廣州百世云倉(cāng)供應(yīng)鏈「赤兔云倉(cāng)供應(yīng)鏈」
- 特別關(guān)注
-
- 獨(dú)立站跨境電商趨勢(shì)「跨境電商獨(dú)立站搭建」
- 亞馬遜好物「亞馬遜值得買的好物」
- 身邊買vr的人越來(lái)越多了都是用來(lái)干些啥「幾十塊的vr和幾千的有什么區(qū)別」
- 你還知道大數(shù)據(jù)在生活中有哪些應(yīng)用「大數(shù)據(jù)在生活中的應(yīng)用舉例」
- 今日頭條去中心化推薦「今日頭條屬于騰訊軟件嗎」
- 跨境電商新手入門書籍「跨境電商創(chuàng)業(yè)相關(guān)書籍」
- 電商美工設(shè)計(jì)班招學(xué)員「學(xué)生會(huì)新媒體運(yùn)營(yíng)部工作計(jì)劃」
- 高密特產(chǎn)泥老虎「高密泥老虎圖片」
- 年輕人前列腺問(wèn)題「前列腺越不想越輕松」
- 京東和國(guó)美合作「國(guó)美和京東合作」
- 熱門點(diǎn)擊
-
- 廣東省中藥材「中藥材首頁(yè)交易」
- “中國(guó)最美小鳥”「世界上最美的小鳥的圖片」
- 憂傷老虎梳毛指南下載「剪羊毛鋼琴曲教學(xué)視頻」
- 老虎現(xiàn)狀及保護(hù)「老虎為什么怕獅子」
- 電商人你慢慢來(lái)「電商人的心酸」
- 速賣通選品「跨境電商賣什么產(chǎn)品比較好」
- 社會(huì)消費(fèi)品零售總額增長(zhǎng)15「7月份社會(huì)消費(fèi)品零售總額24339億元」
- 曲靖網(wǎng)紅人物「湘西網(wǎng)紅小虎」
- 商品摳圖的方法「淘寶摳圖怎么做」
- 為什么電商平臺(tái)需要提供產(chǎn)品質(zhì)檢報(bào)告呢「商家有義務(wù)提供質(zhì)檢報(bào)告嗎」
我經(jīng)常使用的3種有用的設(shè)計(jì)模式「設(shè)計(jì)模式使用心得」
什么是設(shè)計(jì)模式?我們?yōu)槭裁葱枰獙W(xué)習(xí)設(shè)計(jì)模式?
網(wǎng)上已經(jīng)有很多開(kāi)發(fā)者在討論。我不知道你怎么想,但對(duì)我來(lái)說(shuō):設(shè)計(jì)模式是我個(gè)人覺(jué)得可以更好解決問(wèn)題的一種方案。
這意味著什么?如果你開(kāi)發(fā)的項(xiàng)目的功能是固定的,永遠(yuǎn)不會(huì)調(diào)整業(yè)務(wù),那么你就不需要使用設(shè)計(jì)模式等任何技巧。您只需要使用通常的方式編寫代碼并完成需求即可。
但是,我們的開(kāi)發(fā)項(xiàng)目的需求是不斷變化的,這就需要我們經(jīng)常修改我們的代碼。也就是說(shuō),我們現(xiàn)在寫代碼的時(shí)候,需要為未來(lái)業(yè)務(wù)需求可能發(fā)生的變化做好準(zhǔn)備。
這時(shí),你會(huì)發(fā)現(xiàn)使用設(shè)計(jì)模式可以讓你的代碼更具可擴(kuò)展性。
經(jīng)典的設(shè)計(jì)模式有 23 種,但并不是每一種設(shè)計(jì)模式都被頻繁使用。在這里,我介紹我最常用和最實(shí)用的 3 種設(shè)計(jì)模式。
01、策略模式
假設(shè)您目前正在從事一個(gè)電子商務(wù)商店的項(xiàng)目。每個(gè)產(chǎn)品都有一個(gè)原價(jià),我們可以稱之為 originalPrice。但并非所有產(chǎn)品都以原價(jià)出售,我們可能會(huì)推出允許以折扣價(jià)出售商品的促銷活動(dòng)。商家可以在后臺(tái)為產(chǎn)品設(shè)置不同的狀態(tài)。然后實(shí)際售價(jià)將根據(jù)產(chǎn)品狀態(tài)和原價(jià)動(dòng)態(tài)調(diào)整。
具體規(guī)則如下:
部分產(chǎn)品已預(yù)售。為鼓勵(lì)客戶預(yù)訂,我們將在原價(jià)基礎(chǔ)上享受 20% 的折扣。
部分產(chǎn)品處于正常促銷階段。如果原價(jià)低于或等于100,則以10%的折扣出售;如果原價(jià)高于 100,則減 10 美元。
有些產(chǎn)品沒(méi)有任何促銷活動(dòng)。它們屬于默認(rèn)狀態(tài),以原價(jià)出售。
如果你需要寫一個(gè)getPrice函數(shù),你應(yīng)該怎么寫呢?
function getPrice(originalPrice, status){ // ... return price }其實(shí),面對(duì)這樣的問(wèn)題,如果不考慮任何設(shè)計(jì)模式,最直觀的寫法可能就是使用if-else通過(guò)多個(gè)判斷語(yǔ)句來(lái)計(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 }}有三個(gè)條件;然后,我們寫三個(gè) if 語(yǔ)句,這是非常直觀的代碼。
但是這段代碼并不友好。
首先,它違反了單一職責(zé)原則。主函數(shù) getPrice 做了太多的事情。這個(gè)函數(shù)不易閱讀,也容易出現(xiàn)bug。如果一個(gè)條件有bug,整個(gè)函數(shù)就會(huì)崩潰。同時(shí),這樣的代碼也不容易調(diào)試。
然后,這段代碼很難應(yīng)對(duì)變化。正如我在文章開(kāi)頭所說(shuō)的那樣,設(shè)計(jì)模式往往會(huì)在業(yè)務(wù)邏輯發(fā)生變化時(shí)表現(xiàn)出它的魅力。
假設(shè)我們的業(yè)務(wù)擴(kuò)大了,現(xiàn)在還有另一個(gè)折扣促銷:黑色星期五,折扣規(guī)則如下:
價(jià)格低于或等于 100 美元的產(chǎn)品以 20% 的折扣出售。價(jià)格高于 100 美元但低于 200 美元的產(chǎn)品將減少 20 美元。價(jià)格高于或等于 200 美元的產(chǎn)品將減少 20 美元。這時(shí)候怎么擴(kuò)展getPrice函數(shù)呢?
看起來(lái)我們必須在 getPrice 函數(shù)中添加一個(gè)條件。
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)我們?cè)黾踊驕p少折扣時(shí),我們都需要更改函數(shù)。這種做法違反了開(kāi)閉原則。修改已有函數(shù)很容易出現(xiàn)新的錯(cuò)誤,也會(huì)讓getPrice越來(lái)越臃腫。
那么我們?nèi)绾蝺?yōu)化這段代碼呢?
首先,我們可以拆分這個(gè)函數(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)過(guò)這次修改,雖然代碼行數(shù)增加了,但是可讀性有了明顯的提升。我們的main函數(shù)顯然沒(méi)有那么臃腫,寫單元測(cè)試也比較方便。
但是上面的改動(dòng)并沒(méi)有解決根本的問(wèn)題:我們的代碼還是充滿了if-else,當(dāng)我們?cè)黾踊驕p少折扣規(guī)則的時(shí)候,我們?nèi)匀恍枰薷膅etPrice。
想一想,我們之前用了這么多if-else,目的是什么?
實(shí)際上,使用這些 if-else 的目的是為了對(duì)應(yīng)狀態(tài)和折扣策略。
我們可以發(fā)現(xiàn),這個(gè)邏輯本質(zhì)上是一種映射關(guān)系:產(chǎn)品狀態(tài)與折扣策略的映射關(guān)系。
我們可以使用映射而不是冗長(zhǎng)的 if-else 來(lái)存儲(chǔ)映射。比如這樣:
let priceStrategies = { 'pre-sale': preSalePrice, 'promotion': promotionPrice, 'black-friday': blackFridayPrice, 'default': defaultPrice}我們將狀態(tài)與折扣策略結(jié)合起來(lái)。那么計(jì)算價(jià)格會(huì)很簡(jiǎn)單:
function getPrice(originalPrice, status) { return priceStrategies[status](originalPrice)}這時(shí)候如果需要增減折扣策略,不需要修改getPrice函數(shù),我們只需在priceStrategies對(duì)象中增減一個(gè)映射關(guān)系即可。
之前的代碼邏輯如下:
現(xiàn)在代碼邏輯:
這樣是不是更簡(jiǎn)潔嗎?
其實(shí)這招就是策略模式,是不是很實(shí)用?我不會(huì)在這里談?wù)摬呗阅J降臒o(wú)聊定義。如果你想知道策略模式的官方定義,你可以自己谷歌一下。
如果您的函數(shù)具有以下特征:
判斷條件很多。
各個(gè)判斷條件下的代碼相互獨(dú)立
然后,你可以將每個(gè)判斷條件下的代碼封裝成一個(gè)獨(dú)立的函數(shù),接著,建立判斷條件和具體策略的映射關(guān)系,使用策略模式重構(gòu)你的代碼。
02、發(fā)布-訂閱模式
這是我們?cè)陧?xiàng)目中經(jīng)常使用的一種設(shè)計(jì)模式,也經(jīng)常出現(xiàn)在面試中。
現(xiàn)在,我們有一個(gè)天氣預(yù)報(bào)系統(tǒng):當(dāng)極端天氣發(fā)生時(shí),氣象站會(huì)發(fā)布天氣警報(bào)。建筑工地、船舶和游客將根據(jù)天氣數(shù)據(jù)調(diào)整他們的日程安排。
一旦氣象站發(fā)出天氣警報(bào),他們會(huì)做以下事情:
建筑工地:停工船舶:停泊靠岸游客:取消行程如果,我們被要求編寫可用于通知天氣警告的代碼,你會(huì)想怎么做?
編寫天氣警告函數(shù)的常用方法可能是這樣的:
function weatherWarning(){ buildingsite.stopwork() ships.mooring() tourists.canceltrip()}這是一種非常直觀的寫法,但是這種寫法有很多不好的地方:
耦合度太高。建筑工地、船舶和游客本來(lái)應(yīng)該是分開(kāi)的,但現(xiàn)在它們被置于相同的功能中。其中一個(gè)對(duì)象中的錯(cuò)誤可能會(huì)導(dǎo)致其他對(duì)象無(wú)法工作。顯然,這是不合理的。違反開(kāi)閉原則。如果有新的訂閱者加入,那么我們只能修改weatherWarning函數(shù)。造成這種現(xiàn)象的原因是氣象站承擔(dān)了主動(dòng)告知各單位的責(zé)任。這就要求氣象站必須了解每個(gè)需要了解天氣狀況的單位。
但仔細(xì)想想,其實(shí),從邏輯上講,建筑工地、船舶、游客都應(yīng)該依靠天氣預(yù)報(bào),他們應(yīng)該是積極的一方。
我們可以將依賴項(xiàng)更改為如下所示:
氣象站發(fā)布通知,然后觸發(fā)事件,建筑工地、船舶和游客訂閱該事件。
氣象站不需要關(guān)心哪些對(duì)象關(guān)注天氣預(yù)警,只需要直接觸發(fā)事件即可。然后需要了解天氣狀況的單位主動(dòng)訂閱該事件。
這樣,氣象站與訂閱者解耦,訂閱者之間也解耦。如果有新的訂閱者,那么它只需要直接訂閱事件,而不需要修改現(xiàn)有的代碼。
當(dāng)然,為了完成這個(gè)發(fā)布-訂閱系統(tǒng),我們還需要實(shí)現(xiàn)一個(gè)事件訂閱和分發(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)目中存在多對(duì)一的依賴,并且每個(gè)模塊相對(duì)獨(dú)立,那么你可以考慮使用發(fā)布-訂閱模式來(lái)重構(gòu)你的代碼。
事實(shí)上,發(fā)布訂閱模式應(yīng)該是我們前端開(kāi)發(fā)者最常用的設(shè)計(jì)模式。
element.addEventListener('click', function(){ //...})// this is also publish-subscribe pattern03、代理模式
現(xiàn)在我們的頁(yè)面上有一個(gè)列表:
<ul id="container"> <li>Jon</li> <li>Jack</li> <li>bytefish</li> <li>Rock Lee</li> <li>Bob</li> </ul>我們想給頁(yè)面添加一個(gè)效果:每當(dāng)用戶點(diǎn)擊列表中的每個(gè)項(xiàng)目時(shí),都會(huì)彈出一條消息:Hi, I'm ${name}
我們將如何實(shí)現(xiàn)此功能?
大致思路是給每個(gè)li元素添加一個(gè)點(diǎn)擊事件。
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Proxy Pattern</title></head><body> <ul id="container"> <li>Jon</li> <li>Jack</li> <li>bytefish</li> <li>Rock Lee</li> <li>Bob</li> </ul> <script> let container = document.getElementById('container') Array.prototype.forEach.call(container.children, node => { node.addEventListener('click', function(e){ e.preventDefault() alert(`Hi, I'm ${e.target.innerText}`) }) })</script></body></html>這種方法可以滿足要求,但這樣做的缺點(diǎn)是性能開(kāi)銷,因?yàn)槊總€(gè) li 標(biāo)簽都綁定到一個(gè)事件。如果列表中有數(shù)千個(gè)元素,我們是否綁定了數(shù)千個(gè)事件?
如果我們仔細(xì)看這段代碼,可以發(fā)現(xiàn)當(dāng)前的邏輯關(guān)系如下:
每個(gè) li 都有自己的事件處理機(jī)制。但是我們發(fā)現(xiàn)不管是哪個(gè)li,其實(shí)都是ul的成員。我們可以將li的事件委托給ul,讓ul成為這些 li 的事件代理。
這樣,我們只需要為這些 li 元素綁定一個(gè)事件。
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),而是讓代理解決問(wèn)題。
在上述情況下,li 并沒(méi)有直接處理點(diǎn)擊事件,而是將其委托給 ul。
現(xiàn)實(shí)生活中,明星并不是直接出來(lái)談生意,而是交給他們的經(jīng)紀(jì)人,也就是明星的代理人。
代理模式的應(yīng)用非常廣泛,我們來(lái)看另一個(gè)使用它的案例。
假設(shè)我們現(xiàn)在有一個(gè)計(jì)算函數(shù),參數(shù)是字符串,計(jì)算比較耗時(shí)。同時(shí),這是一個(gè)純函數(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)在需要給這個(gè)函數(shù)添加一個(gè)緩存函數(shù):每次計(jì)算后,存儲(chǔ)參數(shù)和對(duì)應(yīng)的結(jié)果。在接下來(lái)的計(jì)算中,會(huì)先從緩存中查詢計(jì)算結(jié)果。
你會(huì)怎么寫代碼?
當(dāng)然,你可以直接修改這個(gè)函數(shù)的功能。但這并不好,因?yàn)榫彺娌⒉皇沁@個(gè)功能的固有特性。如果將來(lái)您不需要緩存,那么,您將不得不再次修改此功能。
更好的解決方案是使用代理模式。
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ù)。
這就是代理模式,它允許我們?cè)诓桓淖冊(cè)紝?duì)象本身的情況下添加額外的功能。
- End -
相關(guān)文章
- 小紅書安全驗(yàn)證老是進(jìn)不去「小紅書老是安全驗(yàn)證」
- 電商為何這么當(dāng)紅「為什么現(xiàn)在的人喜歡網(wǎng)紅」
- 為什么不關(guān)閉電商平臺(tái)「國(guó)家會(huì)取消電商平臺(tái)嗎」
- 電商平臺(tái)云集「云集品」
- 左耳為什么最后李珥和張漾在一起了「一個(gè)男人傷害了深愛(ài)他的女人」
- boseqc35怎么樣「boseQC35II評(píng)測(cè)」
- 耳機(jī)不如買「耳機(jī)踩壞了」
- ebay流量低是什么原因有哪些解決方法「ebay網(wǎng)速很慢怎么解決」
- 耳朵的股價(jià)「盈利與虧損」
- 察右后旗電商服務(wù)中心「察右中旗新聞平臺(tái)」
- 獨(dú)立站和跨境電商平臺(tái)哪個(gè)更好做?「外貿(mào)電商建站哪家好」
- 杭州跨越物流「杭州余杭農(nóng)副物流」
- 羅老師的直播間「有趣的老羅」
- 新手組裝電腦「小白自己組裝電腦」
- 杭州創(chuàng)運(yùn)物流「杭州快順運(yùn)輸有限公司」
- 大學(xué)生對(duì)于直播帶貨的看法「高學(xué)歷女主播」
- 京東在線攢機(jī)「京東diy裝機(jī)在哪里」
- 京東智能京尚云居「京東入股尚品宅配價(jià)格」