JavaScript設計模式之策略模式詳解
什麼是設計模式?為什麼需要學習設計模式?
學習設計模式的目的是:為瞭代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。 設計模式使代碼編寫真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構一樣。
經典的設計模式有 23 種,但並不是每一種設計模式都被頻繁使用。在這裡,介紹最常用和最實用的幾種設計模式,本文先來介紹策略模式(Strategy Pattern)。
策略模式是一種行為設計模式,定義一系列算法,將每一個算法封裝起來,並讓它們可以相互替換。策略模式讓算法獨立於使用它的客戶而變化,也稱為政策模式(Policy)。
假如正在開發一個在線商城的項目,每個產品都有原價,稱之為 originalPrice
。但實際上並非所有產品都以原價出售,可能會推出允許以折扣價出售商品的促銷活動。
商傢可以在後臺為產品設置不同的狀態,然後實際售價將根據產品狀態和原價動態調整。
具體規則如下:
- 部分產品已預售:為鼓勵客戶預訂,將在原價基礎上享受
20%
的折扣。 - 部分產品處於正常促銷階段:如果原價低於或等於
100
,則以10%
的折扣出售;如果原價高於100
,則減10
元。 - 有些產品沒有任何促銷活動:它們屬於
default
狀態,並以原價出售。
這時需要寫一個獲取商品價格的函數 getPrice
,應該怎麼寫呢?
function getPrice(originalPrice, status) { // ... // 返回價格; }
事實上,面對這樣的問題,如果不考慮任何設計模式,最直觀的寫法可能 if-else
多次條件判斷語句來計算價格。
有三種狀態,可以快速編寫如下代碼:
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
語句,這是非常直觀的代碼,但是這段代碼組織上不好。
首先,它違反瞭單一職責原則(Single responsibility principle,規定每個類或者函數都應該有一個單一的功能,並且該功能應該由這個類或者函數完全封裝起來)。函數 getPrice
做瞭太多的事情,這個函數不易閱讀,也容易出現 bug
。如果一個條件出現 bug
,整個函數就會崩潰。同時,這樣的代碼也不容易調試。
並且這段代碼很難應對變化的需求,這時就需要考慮設計模式,其往往會在業務邏輯發生變化時展現出它的魅力。
假設業務擴大瞭,現在還有另一個折扣促銷:黑色星期五。折扣規則如下:
- 價格低於或等於 100 元的產品以 20% 的折扣出售。
- 價格高於 100 元但低於 200 元的產品將減少 20 元。
- 價格高於或等於 200 元的產品將減少 20 元。
這個時候該怎麼擴展 getPrice
函數呢?
看起來必須在 getPrice
函數中添加一個條件語句:
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; } }
每當增加或減少折扣時,都需要更改函數。這種做法違反瞭開閉原則(對擴展開放,對修改關閉)。修改已有的功能很容易出現新的錯誤,而且還會使得 getPrice
越來越臃腫。
那麼如何優化這段代碼呢?
首先,可以拆分這個函數 getPrice
以減少臃腫。
/** * 預售商品價格規則 * @param {*} origialPrice * @returns */ function preSalePrice(origialPrice) { return originalPrice * 0.8; } /** * 促銷商品價格規則 * @param {*} origialPrice * @returns */ function promotionPrice(origialPrice) { if (origialPrice <= 100) { return origialPrice * 0.9; } else { return originalPrice - 20; } } /** * 黑色星期五促銷規則 * @param {*} origialPrice * @returns */ function blackFridayPrice(origialPrice) { if (origialPrice >= 100 && originalPrice < 200) { return origialPrice - 20; } else if (originalPrice >= 200) { return originalPrice - 50; } else { return originalPrice * 0.8; } } /** * 默認商品價格 * @param {*} origialPrice * @returns */ 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); } }
經過這次修改,雖然代碼行數增加瞭,但是可讀性有瞭明顯的提升。getPrice
函數顯然沒有那麼臃腫,寫單元測試也比較方便。
但是上面的改動並沒有解決根本的問題:代碼還是充滿瞭 if-else
,而且當增加或者減少折扣規則的時候,仍然需要修改 getPrice
。
其實使用這些 if-else
的目的就是為瞭對應狀態和折扣策略。
從圖中可以發現,這個邏輯本質上是一種映射關系:產品狀態與折扣策略的映射關系。
可以使用映射而不是冗長的 if-else
來存儲映射,按照這個思路可以構造一個價格策略的映射關系(策略名稱與其處理函數之間的映射),如下:
const priceStrategies = { "pre-sale": preSalePrice, promotion: promotionPrice, "black-friday": blackFridayPrice, default: defaultPrice, };
將狀態與折扣策略結合起來,價格函數就可以優化成如下:
function getPrice(originalPrice, status) { return priceStrategies[status](originalPrice); }
這時候如果需要加減折扣策略,不需要修改函數,隻需要修改價格策略映射關系 priceStrategies
之前的代碼邏輯如下:
優化後的代碼邏輯如下:
以上的優化策略就是使用瞭設計模式之策略模式,在實際的項目開發過程中還是比較實用。
在什麼情況下可以考慮使用策略模式呢?如果函數具有以下特征:
- 判斷條件很多
- 各個判斷條件下的代碼相互獨立
然後可以將每個判斷條件下的代碼封裝成一個獨立的函數,然後建立判斷條件和具體策略的映射關系。
總結
到此這篇關於JavaScript設計模式之策略模式的文章就介紹到這瞭,更多相關JavaScript策略模式內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- 詳解 TypeScript 枚舉類型
- JavaScript設計模式策略模式案例分享
- 14個Python處理Excel的常用操作分享
- JavaScript實現文本相似度對比
- JavaScript工廠模式詳解