模式介紹
通常勺届,我們實(shí)現(xiàn)一個(gè)功能驶俊,可以有多種策略。
我們可以根據(jù)實(shí)際需求來選擇最合適的策略免姿。
針對(duì)這種情況饼酿,一種常規(guī)的方法就是將多種策略封裝在一個(gè)類中,每個(gè)策略對(duì)應(yīng)一個(gè)方法胚膊,在使用時(shí)通過if-else進(jìn)行情況判斷嗜湃,不同情況調(diào)用不同方法來實(shí)現(xiàn)不同的策略奈应。
這種實(shí)現(xiàn)方法我們可以稱之為硬編碼。
然而购披,當(dāng)策略越來越多的時(shí)候杖挣,這個(gè)類就會(huì)變得臃腫,并且由于if-else等復(fù)雜邏輯的存在刚陡,在維護(hù)時(shí)會(huì)更容易產(chǎn)生錯(cuò)誤惩妇,維護(hù)成本就會(huì)變高。
并且這種寫法明顯違反了面向?qū)ο?/a>中的開閉原則筐乳。
如果我們將這些策略的共同點(diǎn)抽象出來歌殃,提供一個(gè)統(tǒng)一的接口,不同的策略有著不同的實(shí)現(xiàn)蝙云,這樣在客戶端就可以通過注入不同的實(shí)現(xiàn)對(duì)象來實(shí)現(xiàn)動(dòng)態(tài)替換氓皱。
這種模式的可擴(kuò)展性、可維護(hù)性都是極高的勃刨。這波材,就是我們要說的策略模式。
總感覺身隐,這模式廷区,不就是多態(tài)嘛。贾铝。
模式定義
針對(duì)一個(gè)功能隙轻,將每一種解決方案封裝起來,而且使它們可以相互替換垢揩,這就是策略模式玖绿。
使用場(chǎng)景
- 針對(duì)同一類型問題的多種處理方式,僅僅是具體行為存在差異叁巨;
- 需要安全地封裝多種同一類型的操作時(shí)斑匪;
- 同一抽象類有多個(gè)子類,同時(shí)又需要使用if-else或swithc-case來選擇具體子類時(shí)俘种。
模式角色
- Strategy:策略抽象。
- ConcreteStrategy:具體策略绝淡,針對(duì)一個(gè)問題宙刘,有多少種策略,就應(yīng)有多少個(gè)具體策略牢酵。
模式示例
2014年12月28日北京提高了公交悬包、地鐵的價(jià)格,不再是單一票價(jià)馍乙,而是分距離計(jì)價(jià)布近。
我們就根據(jù)上述需求垫释,來做一個(gè)出行價(jià)格計(jì)算器。
話不多說撑瞧,我們來實(shí)現(xiàn)這個(gè)功能:
簡(jiǎn)單做了計(jì)算器的UI棵譬,它應(yīng)該提供具體出行方式的選擇,很明顯预伺,我們提供了三種订咸。
并且有一個(gè)出行距離的輸入框。
在獲取到出行方式和出行距離后酬诀,我們就可以來計(jì)算具體價(jià)格脏嚷。
需求很明確,接下來我們要做的就是先實(shí)現(xiàn)各種出行方式下的價(jià)格計(jì)算:
public class PriceCaculateController {
/**
* 根據(jù)出行方式瞒御,選擇距離的計(jì)算方法
*/
public static float cacluatePrice(int km, int typeMode) {
switch (typeMode) {
case Constant.TYPE_BUS:
return caculateBusPrice(km);
case Constant.TYPE_SUBWAY:
return caculateSubwayPrice(km);
case Constant.TYPE_TAX:
return caculateTaxPrice(km);
}
return 0;
}
/**
* 計(jì)算出租車價(jià)格
* 小于3Km父叙,定價(jià)9元,大于3km肴裙,每1km + 1元
*
*/
private static float caculateTaxPrice(int km) {
if (km <= 0) {
return 0;
}
if (km <= 3) {
return 9;
}
return 9 + (km - 3);
}
/**
* 計(jì)算公交車價(jià)格
* 小于5Km趾唱,定價(jià)2元,小于10km践宴,定價(jià)3元鲸匿,其余4元
*/
private static float caculateBusPrice(int km) {
if (km <= 0) {
return 0;
}
if (km <= 5) {
return 2;
}
if (km <= 10) {
return 3;
}
return 4;
}
/**
* 計(jì)算地鐵價(jià)格
* 小于5Km,定價(jià)3元阻肩,小于10km带欢,定價(jià)4元,小于15Km烤惊,定價(jià)5元乔煞,其余6元
*/
private static float caculateSubwayPrice(int km) {
if (km <= 0) {
return 0;
}
if (km <= 5) {
return 3;
}
if (km <= 10) {
return 4;
}
if (km <= 15) {
return 5;
}
return 6;
}
}
PriceCaculateController
中包含了所有出行方式的計(jì)算細(xì)節(jié)。
細(xì)心的同學(xué)可以發(fā)現(xiàn)柒室,我將PriceCaculateController
中的所有計(jì)算方法私有化渡贾,向外暴露了一個(gè)cacluatePrice(int km, int typeMode)
方法。
用戶在使用時(shí)雄右,僅需告訴我們出行距離與出行方式空骚,我們就可以完全自動(dòng)計(jì)算出價(jià)格,下面是使用的代碼:
float price = PriceCaculateController.cacluatePrice(20,1);
這樣我們就實(shí)現(xiàn)了頁面與計(jì)算功能的完美解耦擂仍,頁面完全不關(guān)心計(jì)算的細(xì)節(jié)囤屹。
接下來我們?cè)陧撁嫔咸砑踊A(chǔ)判斷之后,調(diào)用PriceCaculateController.cacluatePrice
即可逢渔。
我們可以來看看具體效果:
至此肋坚,我們的價(jià)格計(jì)算器已經(jīng)開發(fā)完成了。
很明顯,我們的核心代碼全在
PriceCaculateController
中智厌。隨著出行方式的增加诲泌、價(jià)格計(jì)算的優(yōu)化,
PriceCaculateController
必定會(huì)變得臃腫不堪铣鹏。為了將
PriceCaculateController
功能進(jìn)行拆分敷扫,我們就要使用今天講到的策略模式。讓我們來回顧策略模式中的角色:
一個(gè)策略抽象以及多個(gè)具體策略吝沫。
接下來我們就將價(jià)格計(jì)算功能進(jìn)行抽象:
public interface StrategyCaculate {
/**
* 根據(jù)距離計(jì)算價(jià)格
*/
float caculatePrice(int km);
}
策略抽象非常簡(jiǎn)單呻澜。
接下里我們來實(shí)現(xiàn)具體策略,有多少種出行方式惨险,就應(yīng)該有多少種具體策略:
公共汽車(Bus):
public class BusStrategyCaculate implements StrategyCaculate {
@Override
public float caculatePrice(int km) {
if (km <= 0) {
return 0;
}
if (km <= 5) {
return 2;
}
if (km <= 10) {
return 3;
}
return 4;
}
}
出租車(Tax):
public class TaxStrategyCaculate implements StrategyCaculate {
@Override
public float caculatePrice(int km) {
if (km <= 0) {
return 0;
}
if (km <= 3) {
return 9;
}
return 9 + (km - 3);
}
}
地鐵(Subway):
public class SubwayStrategyCaculate implements StrategyCaculate {
@Override
public float caculatePrice(int km) {
if (km <= 0) {
return 0;
}
if (km <= 5) {
return 3;
}
if (km <= 10) {
return 4;
}
if (km <= 15) {
return 5;
}
return 6;
}
}
三種出行方式對(duì)應(yīng)三個(gè)具體策略羹幸。
當(dāng)有新的出行方式時(shí),我們只需創(chuàng)建新的類去實(shí)現(xiàn)策略抽象即可辫愉。
簡(jiǎn)單寫一些測(cè)試代碼:
StrategyCaculate caculate = null;
switch (typeMode) {
case Constant.TYPE_BUS:
caculate = new BusStrategyCaculate();
break;
case Constant.TYPE_SUBWAY:
caculate = new SubwayStrategyCaculate();
break;
case Constant.TYPE_TAX:
caculate = new TaxStrategyCaculate();
break;
}
assert caculate != null;
return caculate.caculatePrice(km);
可以發(fā)現(xiàn)栅受,這里我們又運(yùn)用多態(tài)的特性,根據(jù)不同的出行方式恭朗,創(chuàng)建不同的子類屏镊,接著調(diào)用子類中的價(jià)格計(jì)算。
最后的效果和之前的效果是一致的痰腮。
相關(guān)代碼已經(jīng)提交至GitHub而芥。
總結(jié)
有些人會(huì)有疑惑了,我們最開始的PriceCaculateController
并不復(fù)雜膀值,并且一個(gè)方法對(duì)應(yīng)一種出行方式棍丐,清晰明了,為什么非要改成這個(gè)樣子?
對(duì)于這個(gè)問題沧踏,我們現(xiàn)在的出行方式僅僅有三種歌逢、并且價(jià)格計(jì)算的邏輯非常簡(jiǎn)單。
如果現(xiàn)在繼續(xù)添加出行方式:飛機(jī)翘狱、自駕秘案、高鐵、大巴等潦匈。
如果優(yōu)化出租車的價(jià)格計(jì)算邏輯:出租車3Km以內(nèi)9元阱高,之后每1Km加1元,燃?xì)赓M(fèi)2元茬缩,堵車服務(wù)費(fèi)每分鐘0.1元赤惊,夜晚11點(diǎn)之后價(jià)格提升,每個(gè)城市的價(jià)格不同等等寒屯。
如果將上述邏輯全部寫出荐捻,那么就出租車價(jià)格計(jì)算的邏輯,就要有上百行的代碼寡夹。
如果你們公司是一家專業(yè)的出行價(jià)格計(jì)算公司处面,這些計(jì)算細(xì)節(jié)、出行方式都必定要涵蓋到菩掏。
那么將來魂角,PriceCaculateController
的代碼量,會(huì)有多大智绸?
如果我們運(yùn)用了策略模式野揪,項(xiàng)目的目錄結(jié)構(gòu)就變成了:
當(dāng)某種交通工具的價(jià)格計(jì)算發(fā)生問題,我們僅僅去找對(duì)應(yīng)的具體策略即可瞧栗。并且避免產(chǎn)生了
PriceCaculateController
這種代碼量龐大的類辜梳。策略模式很好地遵循了開閉原則取视,注入不同的具體策略,會(huì)有不同的效果,從而達(dá)到很好的擴(kuò)展性瞒大。
使用策略模式的優(yōu)點(diǎn)有很多:
- 結(jié)構(gòu)清晰明了,使用簡(jiǎn)單厉斟;
- 降低耦合度兼犯,擴(kuò)展方便;
- 封裝徹底锤岸,數(shù)據(jù)更為安全竖幔。
所以,在你能預(yù)見到某個(gè)功能會(huì)有擴(kuò)展的需求時(shí)是偷,并且使用場(chǎng)景符合策略模式的使用場(chǎng)景拳氢,還是強(qiáng)烈建議你使用策略模式的。
感謝
《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》 何紅輝晓猛、關(guān)愛民 著