策略模式的介紹
? 在實(shí)際開(kāi)發(fā)過(guò)程中徽惋,我們常常遇到這樣的問(wèn)題赘理,實(shí)現(xiàn)某一個(gè)功能可以有多種算法或者策略,我們根據(jù)實(shí)際情況選擇不同的算法或者策略來(lái)完成該功能,例如换可,排序算法椎椰,可以選擇插入排序、冒泡排序等沾鳄。
? 針對(duì)這種情況慨飘,一種常規(guī)的方法是將多種算法寫在一個(gè)類中。例如译荞,需要提供多種排序算法瓤的,可以將這些算法寫到一個(gè)類中,每個(gè)方法對(duì)應(yīng)一個(gè)具體的排序算法磁椒;當(dāng)然也可以將這些排序算法封裝在一個(gè)統(tǒng)一的方法中堤瘤,通過(guò)if...else...
或者case
等條件判斷語(yǔ)句來(lái)選擇具體的算法玫芦。這兩種實(shí)現(xiàn)方法我們都可以稱為硬編碼浆熔。然而當(dāng)很多個(gè)算法集中在一個(gè)類中時(shí),這個(gè)類就會(huì)變的臃腫桥帆,這個(gè)類的維護(hù)成本會(huì)變高医增,在維護(hù)是也更容易引發(fā)錯(cuò)誤。如果需要增加一種新的排序算法老虫,需要修改封裝算法類的源代碼叶骨。這就明顯違反了我們上面所說(shuō)的OCP
原則和單一原則。
? 如果將這些算法或者策略抽象出來(lái)祈匙,提供一個(gè)統(tǒng)一的接口忽刽,不同的算法或者策略有不同的實(shí)現(xiàn)類,這樣在程序客戶端就可以通過(guò)注入不同的實(shí)現(xiàn)對(duì)象來(lái)實(shí)現(xiàn)算法或者策略的動(dòng)態(tài)替換夺欲,這種模式的可擴(kuò)展性跪帝、可維護(hù)性也就更高,也就是我們本文要說(shuō)的策略模式些阅。
策略模式的定義
策略模式定義了一系列的算法伞剑,并將每一個(gè)算法封裝起來(lái),而且使它們還可以相互替換市埋。策略模式讓算法獨(dú)立于使用它的客戶而獨(dú)立變化黎泣。
策略模式的使用場(chǎng)景
- 針對(duì)同一類型問(wèn)題的多種處理方式,僅僅是具體行為有差別
- 需要安全地封裝多種同一類型的操作
- 出現(xiàn)同一抽象類有多個(gè)子類缤谎,而又要使用if- else或者switch-case來(lái)選擇具體子類時(shí)
策略模式的UML類圖
UML類圖如下圖所示;
角色介紹
- Context -用來(lái)操作策略的上下文環(huán)境
- Stragety -策略的抽象
- ConcreteStragetyA抒倚、ConcreteStragetyB -具體的策略類實(shí)現(xiàn)。
策略模式的簡(jiǎn)單實(shí)現(xiàn)
通常一個(gè)問(wèn)題有多個(gè)解決方案時(shí)坷澡,最簡(jiǎn)單的方式就是利用if-else或者switch-case方式根據(jù)不同的情景選擇不同的解決方案托呕,但這種簡(jiǎn)單的方案問(wèn)題太多,例如耦合性太高、代碼臃腫镣陕、難以維護(hù)等谴餐,但是,如果解決方案中包括大量的處理邏輯需要封裝呆抑,或者處理方式變動(dòng)較大的時(shí)候則就顯得混亂岂嗓、復(fù)雜,當(dāng)需要增加一種方案時(shí)就需要修改類中的代碼鹊碍。
是的厌殉,if-else
這種方式確實(shí)不會(huì)遵循開(kāi)閉原則,而應(yīng)對(duì)這種情況策略模式能很好的解決這類問(wèn)題侈咕,它將各種方案分離開(kāi)來(lái)公罕,讓程序客戶端根據(jù)具體的需求來(lái)動(dòng)態(tài)地選擇不同的策略方案。
下面我們以北京坐公共交通工具的費(fèi)用計(jì)算來(lái)演示一個(gè)簡(jiǎn)單實(shí)例耀销。
import org.junit.Test;
public class PriceCalculator {
//公交車類型
private static final int BUS = 1;
//地鐵類型
private static final int SUBWAY = 2;
@Test
public void main() {
PriceCalculator calculator = new PriceCalculator();
System.out.println("坐16km的公交價(jià)格為:" + calculator.calculatePrice(16,SUBWAY));
}
/**
* 公交車 十公里內(nèi)一元楼眷,超過(guò)十公里之后每加一元可以乘5公里
* @param km
* @return
*/
private int busPrice(int km) {
//超過(guò)十公里的總距離
int extraTotal = km - 10;
//超過(guò)五公里的倍數(shù)
int extraFactor = extraTotal / 5;
//超過(guò)的距離對(duì)5公里取余
int factor = extraTotal % 5;
//計(jì)算價(jià)格
int price = extraFactor + 1;
return factor > 0 ? ++price : price;
}
/**
* 6公里(含) 內(nèi)3元:6-12公里(含) 4元。12-22公里(含) 5元熊尉,22-32公里(含) 6元
* @return
*/
private int subwayPrice(int km){
if (km<=6){
return 3;
}else if (km>6&&km<=12){
return 4;
}else if (km>12&&km<=22){
return 5;
}else if(km>22&&km<=32){
return 6;
}
return 7;
}
int calculatePrice(int km,int type){
if (type==BUS){
return busPrice(km);
}else if (type==SUBWAY){
return subwayPrice(km);
}
return 0;
}
}
PriceCalculator
類很明顯的問(wèn)題就是并不是單一原則罐柳,首先它承擔(dān)了計(jì)算公交車和地鐵乘坐價(jià)格的職責(zé);另一個(gè)問(wèn)題就是通過(guò)if-else
的形式來(lái)判斷使用哪種計(jì)算形式狰住,當(dāng)我們?cè)黾右环N出行方式時(shí)张吉,如出租車,那么我們就需要在PriceCalculator
中增加一個(gè)方法來(lái)計(jì)算出租車出行的價(jià)格催植,并且在calculatePrice
函數(shù)中增加一個(gè)判斷肮蛹,代碼添加后大致如下:
//出租車類型
private static final int TAXI = 3;
/**
* 出租車價(jià)格
* @param km
* @return
*/
private int taxiPrice(int km) {
return km * 2;
}
int calculatePrice(int km, int type) {
if (type == BUS) {
return busPrice(km);
} else if (type == SUBWAY) {
return subwayPrice(km);
}else if(type==TAXI){
return taxiPrice(km);
}
return 0;
}
此時(shí)代碼已經(jīng)比較混亂,各種if-else
語(yǔ)句纏繞其中创南,當(dāng)價(jià)格的計(jì)算方法變化時(shí)伦忠,需要直接修改這個(gè)類中的代碼,那么很有可能有一段代碼是其他幾個(gè)計(jì)算方法所共同使用的扰藕,這就容易引入錯(cuò)誤缓苛。另外在增加出行方式時(shí),我們又需要在calculatePrice中添加if-else
邓深,此時(shí)很有可能就是復(fù)制上一個(gè)if-else
然后手動(dòng)進(jìn)行修改未桥,手動(dòng)復(fù)制代碼也是容易引入錯(cuò)誤的做法之一。這類代碼是難以應(yīng)對(duì)變化的芥备,它會(huì)使代碼變得越來(lái)越臃腫冬耿,難以維護(hù),我們解決這類問(wèn)題的手法也就是本篇談到的策略模式萌壳。
? 下面我們對(duì)上面示例進(jìn)行重構(gòu)亦镶。
? 首先我們需要定義一個(gè)抽象的價(jià)格計(jì)算接口日月,這里命名為CalculateStrategy
,具體代碼如下:
public interface CalulateStrategy {
/**
* 按照距離計(jì)算價(jià)格
* @param km
* @return
*/
int calculatePprice(int km);
}
對(duì)于每一種出行方式我們都有一個(gè)獨(dú)立的計(jì)算策略類缤骨,這些策略類都實(shí)現(xiàn)了CalculateStrategy
接口爱咬,例如下面是公交車和地鐵的計(jì)算策略類。
public class BusStrategy implements CalculateStrategy {
/**
* 公交車 十公里內(nèi)一元绊起,超過(guò)十公里之后每加一元可以乘5公里
*
* @param km
* @return
*/
@Override
public int calculatePprice(int km) {
//超過(guò)十公里的總距離
int extraTotal = km - 10;
//超過(guò)五公里的倍數(shù)
int extraFactor = extraTotal / 5;
//超過(guò)的距離對(duì)5公里取余
int factor = extraTotal % 5;
//計(jì)算價(jià)格
int price = extraFactor + 1;
return factor > 0 ? ++price : price;
}
}
public class SubwayStrategy implements CalculateStrategy {
/**
* 6公里(含) 內(nèi)3元:6-12公里(含) 4元精拟。12-22公里(含) 5元,22-32公里(含) 6元
*
* @return
*/
@Override
public int calculatePprice(int km) {
if (km <= 6) {
return 3;
} else if (km > 6 && km <= 12) {
return 4;
} else if (km > 12 && km <= 22) {
return 5;
} else if (km > 22 && km <= 32) {
return 6;
}
return 7;
}
}
我們?cè)趧?chuàng)建一個(gè)扮演Context角色的類虱歪,這里將它命名為TranficCalculator
,具體代碼如下蜂绎。
public class TranficCalculator {
@Test
public void main() {
TranficCalculator calculator = new TranficCalculator();
//設(shè)置計(jì)算策略
calculator.setmStratefy(new SubwayStrategy());
System.out.println("公交車乘16公里的價(jià)格:"+calculator.calculatePrice(16));
}
CalculateStrategy mStratefy;
public int calculatePrice(int km){
return mStratefy.calculatePrice(km);
}
public void setmStratefy(CalculateStrategy mStratefy) {
this.mStratefy = mStratefy;
}
}
經(jīng)過(guò)上述重構(gòu)之后,去掉了各種各樣的if-else
語(yǔ)句笋鄙,結(jié)構(gòu)變得也很清晰师枣,其結(jié)構(gòu)如圖所示
這種方案在隱藏實(shí)現(xiàn)的同時(shí),可擴(kuò)展線變得很強(qiáng)萧落,例如我們要增加出租車的計(jì)算策略時(shí)践美,只需要添加一個(gè)出租車的計(jì)算策略類,然后將該策略設(shè)置給TranficCalculator
最后直接通過(guò)TranficCalculator
對(duì)象的計(jì)算方法即可铐尚。例如下面代碼:
public class TaxiStrategy implements CalculateStrategy {
@Override
public int calculatePrice(int km) {
return km*2;
}
}
public class TranficCalculator {
@Test
public void main() {
TranficCalculator calculator = new TranficCalculator();
//設(shè)置計(jì)算策略
calculator.setmStratefy(new TaxiStrategy());
System.out.println("公交車乘16公里的價(jià)格:"+calculator.calculatePrice(16));
}
CalculateStrategy mStratefy;
public int calculatePrice(int km){
return mStratefy.calculatePrice(km);
}
public void setmStratefy(CalculateStrategy mStratefy) {
this.mStratefy = mStratefy;
}
}
通過(guò)上面的實(shí)例我們可以清晰地看出二者的區(qū)別所在拨脉。前者通過(guò)if-else
來(lái)解決問(wèn)題,雖然實(shí)現(xiàn) 較為簡(jiǎn)單宣增,類型層級(jí)單一,但暴露的問(wèn)題非常明顯矛缨,即代碼臃腫爹脾,邏輯復(fù)雜,難以升級(jí)和維護(hù)箕昭,沒(méi)有結(jié)構(gòu)可言灵妨;后者則是通過(guò)建立抽象,將不同的策略構(gòu)建成換一個(gè)具體的策略實(shí)現(xiàn)落竹,通過(guò)不同的策略實(shí)現(xiàn)算法的替換泌霍。在簡(jiǎn)化邏輯、結(jié)構(gòu)的同時(shí)述召,增強(qiáng)了系統(tǒng)的可讀性朱转、穩(wěn)定性、可擴(kuò)展性积暖,這對(duì)于較為復(fù)雜的業(yè)務(wù)邏輯顯得更為直觀藤为,擴(kuò)展也更為方便。
總結(jié)
策略模式主要用于分離算法夺刑,在相同的行為抽象下有不同的具體實(shí)現(xiàn)策略缅疟。這個(gè)模式很好地演示了開(kāi)閉原則分别,也就是定義抽象,注入不同的實(shí)現(xiàn)存淫,從而達(dá)到很好的擴(kuò)展性耘斩。
優(yōu)點(diǎn)
- 結(jié)構(gòu)清晰明了、使用簡(jiǎn)單直觀
- 耦合度相對(duì)而言較低桅咆,擴(kuò)展方便
- 操作封裝也更為徹底煌往,數(shù)據(jù)更加安全
缺點(diǎn)
- 隨著策略的增加,子類會(huì)變的繁多
Demo
參考
《Android源碼設(shè)計(jì)模式》