一、前言
策略模式可以定義一系列的演算法,並將每一個演算法封裝起來,而且使它們還可以相互替換。策略模式讓演算法獨立於使用它的客戶而獨立變化。
二、策略模式的使用場景
- 針對同一型別問題的多種處理方式,僅僅是具體行為有差別時。
- 需要安全地封裝多種同一型別的操作時。
- 出現同一個抽象類有多個子類,而又需要使用if-else或者switch-case來選擇具體子類時。
三、策略模式的實現
通常一個問題有多個解決方案時,最簡單的方式就是利用if-else或者switch-case方式根據不同的情景選擇不同的解決方案,但這種簡單的方案問題太多,例如耦合性太高、程式碼臃腫、難維護等。但是如果解決方案中包括大量的處理邏輯需要封裝,或者處理方式變動較大的時候則就顯得混亂、複雜,當需要增加一種方案時就需要修改類中的程式碼。
使用if-else這種方法確實不會遵循開閉原則,而應對這種情況策略模式就能很好地解決這類問題,它將各種方案分離開來,讓程式客戶端根據具體的需求來動態地選擇不同的策略方案。
下面我們通過交通工具來進行費用計算演示,對於部分城市的公交都是實行分時段計價,坐得越遠費用越高。顯然公交與地鐵的價格計算是不一樣的,我們算一下乘坐不同出行工具的成本,程式碼:
public class PriceCalculator {
private static final int BUS = 1;
private static final int SUBWAY = 2;
public static void main(String[] strings) {
PriceCalculator calculator = new PriceCalculator();
System.out.println("做20km公交票價:" + calculator.calculatePrice(20, BUS));
System.out.println("做20km地鐵票價:" + calculator.calculatePrice(20, SUBWAY));
}
/**
* 公交車計價 10公里之內1塊錢,超過10公里每加一塊可以乘5公里
*
* @param km
* @return
*/
private int busPrice(int km) {
//超過10公里的距離
int extraTotal = km - 10;
//超過距離是5km的倍數
int extraFactor = extraTotal / 5;
//超過的距離對5km取餘
int fraction = extraTotal % 5;
//價格計算
int price = 1 + extraFactor % 5;
return fraction > 0 ? ++price : price;
}
/**
* 6km內3塊,6—12km是4塊,12—22km是5塊,22-32km是6塊,其他距離7塊
*
* @param km
* @return
*/
public 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;
}
/**
* 根據不同型別計算
*
* @param km
* @param type
* @return
*/
int calculatePrice(int km, int type) {
if (type == BUS) {
return busPrice(km);
} else if (type == SUBWAY) {
return subwayPrice(km);
}
return 0;
}
}複製程式碼
PriceCalculator 類很明顯的問題就是並不是單一職責,首先它承擔計算公交車和地鐵乘坐價格的職責,另一個問題就是通過if-else的形式來判斷使用哪種計算形式。當我們增加一種出行方式時,如計程車,那麼我們就需要在PriceCalculator 中增加一個方法來計算計程車出行的價格,並且在calculatePrice(int km, int type)函式增加一個判斷,程式碼:
public class PriceCalculator {
private static final int TAXI = 3;
/***
* 每公里2塊
*
* @param km
* @return
*/
private int taixPrice(int km) {
return km * 2;
}
/**
* 根據不同型別計算
*
* @param km
* @param type
* @return
*/
int calculatePrice(int km, int type) {
if (type == BUS) {
return busPrice(km);
} else if (type == SUBWAY) {
return subwayPrice(km);
} else if (type == TAXI) {
return taixPrice(km);
}
return 0;
}
}複製程式碼
此時程式碼已經很混亂,各種if-else語句纏繞在一起。當價格的計算方法變化時,需要直接修改這個類中的程式碼,那麼很可能有一段程式碼是其他幾個計算方法所公共使用的,這就容易引入錯誤。另外增加出行方式時,我們又需要在calculatePrice中新增if-else,此時很有可能就是複製一個if-else,然後手動進行修改,手動複製程式碼也是最容易引入錯誤的做法之一。這類程式碼必然是難以應對變化的,它會使得程式碼變得越來越臃腫,難以維護,我們解決這類問題的手法也就是使用策略模式。我們也可以把每種計算方式獨立成一個函式,然後外部呼叫對應的方法即可,但這也是另一種耦合的形式,對於可變性較大的演算法來說還是不適合使用這種方式。
接下來使用策略模式對以上進行重構。首先定義一個抽象的價格計算介面:
//計算介面
public interface CalculateStrategy {
//按距離來計算價格
int calculatePrice(int km);
}複製程式碼
對於每一種出行方式我們都有一個獨立的計算策略類,這些策略類都實現了CalculateStrategy 介面,下面是公交車與地鐵的計算策略類:
public class BusStrategy implements CalculateStrategy {
@Override
public int calculatePrice(int km) {
int extraTotal = km - 10;
int extraFactor = extraTotal / 5;
int fraction = extraTotal % 5;
int price = 1 + extraTotal % 5;
return fraction > 0 ? ++price : price;
}
}複製程式碼
public class SubwayStrategy implements CalculateStrategy {
@Override
public int calculatePrice(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;
}
}複製程式碼
我們再建立一個扮演Context角色的類,這裡將命名為TranficCalculator,程式碼:
public class TranficCalculator {
private CalculateStrategy strategy;
public void setStrategy(CalculateStrategy strategy) {
this.strategy = strategy;
}
public int calclatePrice(int km) {
return strategy.calculatePrice(km);
}
public static void main(String[] strings) {
TranficCalculator calculator = new TranficCalculator();
calculator.setStrategy(new BusStrategy());
System.out.println("公交車20km價格:" + calculator.calclatePrice(20));
}
}複製程式碼
這種方式在隱藏實現的同時,可擴充套件性變得很強,當我們需要新增計程車的計算策略時,只需要新增一個計程車計算策略類,然後將該策略設定給TranficCalculator ,最好直接通過TranficCalculator 物件的計算方法即可。程式碼:
public class TaxiStratrategy implements CalculateStrategy {
@Override
public int calculatePrice(int km) {
return km * 2;
}
}複製程式碼
將策略注入TranficCalculator中。
public class TranficCalculator {
private CalculateStrategy strategy;
public void setStrategy(CalculateStrategy strategy) {
this.strategy = strategy;
}
public int calclatePrice(int km) {
return strategy.calculatePrice(km);
}
public static void main(String[] strings) {
TranficCalculator calculator = new TranficCalculator();
calculator.setStrategy(new TaxiStratrategy());
System.out.println("計程車20km價格:" + calculator.calclatePrice(20));
}
}複製程式碼
通過以上清晰的看出二者的區別。前者通過if-else來解決問題,實現簡單,層級單一,暴露問題多,程式碼臃腫,邏輯複雜,難以升級與維護,沒有結構可言;後者通過建立抽象,不同的策略建成一個具體的策略實現,通過不同的策略實現演算法替換。簡化邏輯、結構的同時,增強系統的可讀性、穩定性、可擴充套件性,對於較複雜的邏輯顯得更為直觀,擴充套件更為方便。
四、Android原始碼中使用的策略模式
在動畫執行過程中,我們需要一些動態效果,有點類似電影的慢鏡頭,有時需要慢一點,有時需要快一點,這樣動畫變得靈動起來,這些動態效果就是通過插值器(TimeInterpolator)實現的,我們只需要對Animation物件設定不同的插值器就可以實現不同的動態效果,該效果則使用的就是策略模式實現。
- AccelerateDecelerateInterpolator 在動畫開始與介紹的地方速率改變比較慢,在中間的時候加速
- AccelerateInterpolator 在動畫開始的地方速率改變比較慢,然後開始加速
- AnticipateInterpolator 開始的時候向後然後向前甩
- AnticipateOvershootInterpolator 開始的時候向後然後向前甩一定值後返回最後的值
- BounceInterpolator 動畫結束的時候彈起
- CycleInterpolator 動畫迴圈播放特定的次數,速率改變沿著正弦曲線
- DecelerateInterpolator 在動畫開始的地方快然後慢
- LinearInterpolator 以常量速率改變
- OvershootInterpolator 向前甩一定值後再回到原來位置
- PathInterpolator 路徑插值器