做前端開發已經好幾年了,對設計模式一直沒有深入學習總結過。隨著架構相關的工作越來越多,越來越能感覺到設計模式成為了我前進道路上的一個阻礙。所以從今天開始深入學習和總結經典的設計模式以及面向對象的幾大原則。 今天第一天,首先來講策略模式。 什麼是策略模式? GoF四兄弟的經典《設計模式》中,對策略模式 ...
做前端開發已經好幾年了,對設計模式一直沒有深入學習總結過。隨著架構相關的工作越來越多,越來越能感覺到設計模式成為了我前進道路上的一個阻礙。所以從今天開始深入學習和總結經典的設計模式以及面向對象的幾大原則。
今天第一天,首先來講策略模式。
什麼是策略模式?
GoF四兄弟的經典《設計模式》中,對策略模式的定義如下:
定義一系列的演算法,把它們一個個封裝起來,並且使它們可互相替換。
上邊這句話,從字面來看很簡單。但是如何在開發過程中去應用,僅憑一個定義依然是一頭霧水。以筆者曾經做過的商戶進銷存系統為例:
某超市準備舉行促銷活動,市場人員經過調查分析制定了一些促銷策略:
- 購物滿100減10
- 購物滿200減30
- 購物滿300減50
- 。。。
收銀軟體的界面是這樣的(簡單示意):
我們應該如何計算實際消費金額?
最初的實現是這樣的:
//方便起見,我們把各個促銷策略定義為枚舉值:0,1,2...
var getActualTotal = function(onSaleType,originTotal){
if(onSaleType===0){
return originTotal-Math.floor(originTotal/100)*10
}
if(onSaleType===1){
return originTotal-Math.floor(originTotal/200)*30
}
if(onSaleType===0){
return originTotal-Math.floor(originTotal/300)*50
}
}
getActualTotal(1,2680); //2208
上面這段代碼很簡單,而且缺點也很明顯。隨著我的滿減策略逐漸增多,getActualTotal
函數會越變越大,而且充滿了if
判斷,稍一疏忽就容易弄錯。
OK,有人說我很懶,雖然這樣不夠優雅但並不影響我的使用,畢竟滿減策略再多也多不到哪去。
我只能說,需求永遠不是程式員定的。。這時,市場人員說我們新版程式添加了會員功能,我們需要支持以下的促銷策略:
會員促銷策略:
- 會員充300返60,且首單打9折
- 會員充500返100,且首單打8折
- 會員充1000返300,且首單打7折
- ...
這個時候,如果你還在原先的getActualTotal
函數中繼續添加if
判斷,我想如果你的領導review你這段代碼,可能會懷疑自己當初怎麼把你招進來。。
OK,我們終於下定決心要重構促銷策略的代碼,我們可以這麼做:
var vipPolicy_0=function(originTotal){
return originTotal-Math.floor(originTotal/100)*10
}
var vipPolicy_1=function(originTotal){
return originTotal-Math.floor(originTotal/200)*30
}
...
//會員充1000返300
var vipPolicy_10=function(account,originTotal){
if(account===0){
account+=1300;
return originTotal*0.9
}else{
account+=1300;
return originTotal;
}
return originTotal-Math.floor(originTotal/200)*30
}
...
var vipPolicy_n=function(){
...
}
var getActualTotal=function(onSaleType,originTotal,account){
switch(onSaleType){
case 0:
return vipPolicy_0(originTotal);
case 1:
return vipPolicy_0(originTotal);
...
case n:
return ...
default:
return originTotal;
}
}
好了,現在我們每種策略都有自己獨立的空間了,看起來井井有條。但是還有兩個問題沒有解決:
- 隨著促銷策略的增加,
getActualTotal
的代碼量依然會越來越大 - 系統缺乏彈性,如果需要增加一種策略,那麼除了添加一個策略函數,還需要修改
switch...case..
語句
讓我們再來回顧一下策略模式的定義:
定義一系列的演算法,把它們一個個封裝起來,並且使它們可互相替換
在我們的例子中,每種促銷策略的實現方式是不一樣的,但我們最終的目的都是為了求得實際金額。策略模式可以把我們對促銷策略的演算法一個個封裝起來,並且使它們可互相替換而不影響我們對實際金額的求值,這正好是我們所需要的。
下麵我們用策略模式來重構上面的代碼:
var policies={
"Type_0":function(originTotal){
return originTotal-Math.floor(originTotal/100)*10
},
"Type_1":function(originTotal){
return originTotal-Math.floor(originTotal/200)*30
},
...
"Type_n":function(originTotal){
...
}
}
var getActualTotal=function(onSaleType,originTotal,account){
return policies["Type_"+onSaleType](originTotal,account)
}
//執行
getActualTotal(0,2680.00);//2208
分析上面的代碼我們發現,不管促銷策略如何增加,getActualTotal
函數完全不需要再變化了。我們要做的,就是增加新策略的函數而已。
通過策略模式的代碼,我們消除了讓人反胃的大片條件分支語句,getActualTotal
本身並沒有計算能力,而是將計算全權委托給了策略函數。
由此我們可以總結出策略模式實現的要點:
- 將變化的演算法封裝成獨立的策略函數,並負責具體的計算
- 委托函數,該函數接受客戶請求,並將請求委托給某一個具體的策略函數
用一張UML圖表示如下:
怎麼樣?現在看到上面這張圖是不是有了瞭然於胸的感覺?那就趕緊去試一試策略模式吧!
參考書籍:
- 《設計模式:可復用面向對象軟體的基礎》
- 《大話設計模式》
- 《Javascript設計模式與開發實踐》