前言
策略模式屬于對(duì)象的行為模式。其用意是針對(duì)一組算法,將每一個(gè)算法封裝到具有共同接口的獨(dú)立的類中,從而使得它們可以相互替換。策略模式使得算法可以在不影響到客戶端的情況下發(fā)生變化。
背景
在軟件開發(fā)中常常遇到這種情況,實(shí)現(xiàn)某一個(gè)功能有多種算法或者策略,我們可以根據(jù)環(huán)境或者條件的不同選擇不同的算法或者策略來完成該功能。如查找、排序等,一種常用的方法是硬編碼(Hard Coding)在一個(gè)類中,如需要提供多種查找算法,可以將這些算法寫到一個(gè)類中,在該類中提供多個(gè)方法,每一個(gè)方法對(duì)應(yīng)一個(gè)具體的查找算法;當(dāng)然也可以將這些查找算法封裝在一個(gè)統(tǒng)一的方法中,通過if…else…或者case等條件判斷語句來進(jìn)行選擇。
這兩種實(shí)現(xiàn)方法我們都可以稱之為硬編碼,如果需要增加一種新的查找算法,需要修改封裝算法類的源代碼;更換查找算法,也需要修改客戶端調(diào)用代碼。在這個(gè)算法類中封裝了大量查找算法,該類代碼將較復(fù)雜,維護(hù)較為困難。如果我們將這些策略包含在客戶端,這種做法更不可取,將導(dǎo)致客戶端程序龐大而且難以維護(hù),如果存在大量可供選擇的算法時(shí)問題將變得更加嚴(yán)重。
問題
如何讓算法和對(duì)象分開來,使得算法可以獨(dú)立于使用它的客戶而變化?
方案
把一個(gè)類中經(jīng)常改變或者將來可能改變的部分提取出來,作為一個(gè)接口,然后在類中包含這個(gè)對(duì)象的實(shí)例,這樣類的實(shí)例在運(yùn)行時(shí)就可以隨意調(diào)用實(shí)現(xiàn)了這個(gè)接口的類的行為。
比如定義一系列的算法,把每一個(gè)算法封裝起來, 并且使它們可相互替換,使得算法可獨(dú)立于使用它的客戶而變化。這就是策略模式。
適用情況
許多相關(guān)的類僅僅是行為有異。 “策略”提供了一種用多個(gè)行為中的一個(gè)行為來配置一個(gè)類的方法。即一個(gè)系統(tǒng)需要?jiǎng)討B(tài)地在幾種算法中選擇一種。
當(dāng)一個(gè)應(yīng)用程序需要實(shí)現(xiàn)一種特定的服務(wù)或者功能,而且該程序有多種實(shí)現(xiàn)方式時(shí)使用。
一個(gè)類定義了多種行為 , 并且這些行為在這個(gè)類的操作中以多個(gè)條件語句的形式出現(xiàn)。將相關(guān)的條件分支移入它們各自的Strategy類中以代替這些條件語句。、
優(yōu)點(diǎn)
- 可以動(dòng)態(tài)的改變對(duì)象的行為
- 策略模式提供了管理相關(guān)的算法族的辦法。策略類的等級(jí)結(jié)構(gòu)定義了一個(gè)算法或行為族。恰當(dāng)使用繼承可以把公共的代碼移到父類里面,從而避免代碼重復(fù)。
- 使用策略模式可以避免使用多重條件(if-else)語句。多重條件語句不易維護(hù),它把采取哪一種算法或采取哪一種行為的邏輯與算法或行為的邏輯混合在一起,統(tǒng)統(tǒng)列在一個(gè)多重條件語句里面,比使用繼承的辦法還要原始和落后。
缺點(diǎn)
- 客戶端必須知道所有的策略類,并自行決定使用哪一個(gè)策略類
- 策略模式將造成產(chǎn)生很多策略類
組成
- 環(huán)境類(Context):用一個(gè)ConcreteStrategy對(duì)象來配置。維護(hù)一個(gè)對(duì)Strategy對(duì)象的引用??啥x一個(gè)接口來讓Strategy訪問它的數(shù)據(jù)。
- 抽象策略類(Strategy):定義所有支持的算法的公共接口。 Context使用這個(gè)接口來調(diào)用某ConcreteStrategy定義的算法。
- 具體策略類(ConcreteStrategy):以Strategy接口實(shí)現(xiàn)某具體算法。
應(yīng)用
模擬一下城市交通系統(tǒng),假設(shè)情況如下:
公交:起步2元,超過5公里每公里加1元。
地鐵:5公里內(nèi)3元,5-10公里4元,最多5元。
出租:每公里2元。
先聲明一個(gè)接口,聲明一個(gè)計(jì)算的方法
public interface CalculateStrategy {
int calculatePrice(int km);
}
實(shí)現(xiàn)三個(gè)具體的計(jì)算方法
public class BusCalculate implements CalculateStrategy {
@Override
public int calculatePrice(int km) {
int busP = 2;
if (km>5)
busP = busP+km-5;
return busP;
}
}
public class SubwayCalculate implements CalculateStrategy {
@Override
public int calculatePrice(int km) {
int subwayP=0;
if (km<5)
subwayP = 3;
if (km>5&&km<=10)
subwayP=4;
if (km>10)
subwayP=5;
return subwayP;
}
}
public class TaxiCalculate implements CalculateStrategy {
@Override
public int calculatePrice(int km) {
return 2 * km;
}
}
然后看看現(xiàn)在的計(jì)算類,這個(gè)是重點(diǎn)
public class PriceCalculate2 {
private CalculateStrategy calculateStrategy;
public void setCalculateStrategy(CalculateStrategy calculateStrategy){
this.calculateStrategy=calculateStrategy;
}
public int getPrice(int km){
return calculateStrategy.calculatePrice(km);
}
}
客戶端調(diào)用
public class Main {
public static void main(String[] args) {
PriceCalculate2 priceCalculate2 = new PriceCalculate2();
priceCalculate2.setCalculateStrategy(new SubwayCalculate());
int price = priceCalculate2.getPrice(10);
System.out.println(price);
}
}
這里重點(diǎn)就是計(jì)算類了,可以看到里面的解法非常簡(jiǎn)便。而且如果要新增其他算法的話,直接實(shí)現(xiàn)一個(gè)接口就可以了,其他類都不需要變化。便于之后的升級(jí)或擴(kuò)展。
Android源碼中的策略模式
看一下一般使用屬相動(dòng)畫時(shí)候的樣子:
Animation animation = new AlphaAnimation(1,0);
animation.setInterpolator(new AccelerateDecelerateInterpolator());
imageView.setAnimation(animation);
animation.start();
animation.setInterpolator()其實(shí)用的就是策略模式,Android中有很多不同的插值器,都實(shí)現(xiàn)了Interpolator接口。
然后通過setInterpolator設(shè)置給Animation。
在動(dòng)畫執(zhí)行的時(shí)候,會(huì)通過設(shè)置的計(jì)時(shí)器來在不同的動(dòng)畫時(shí)間執(zhí)行不同的動(dòng)畫效果。
public void setInterpolator(Interpolator i) {
mInterpolator = i;
}
protected void ensureInterpolator() {
if (mInterpolator == null) {
//默認(rèn)就是加速度插值器,在Animation初始化的時(shí)候執(zhí)行。
mInterpolator = new AccelerateDecelerateInterpolator();
}
}
public boolean getTransformation(long currentTime, Transformation outTransformation) {
......
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
......
//通過策略模式來獲取不同的值。
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
applyTransformation(interpolatedTime, outTransformation);
}
......
}
在動(dòng)畫執(zhí)行過程中,我們需要一些動(dòng)態(tài)效果,有點(diǎn)類似電影的慢鏡頭,有時(shí)需要慢一點(diǎn),有時(shí)需要快一點(diǎn),這樣動(dòng)畫變得靈動(dòng)起來,這些動(dòng)態(tài)效果就是通過插值器(TimeInterpolator)實(shí)現(xiàn)的,我們只需要對(duì)Animation對(duì)象設(shè)置不同的插值器就可以實(shí)現(xiàn)不同的動(dòng)態(tài)效果,該效果則使用的就是策略模式實(shí)現(xiàn)。
- AccelerateDecelerateInterpolator 在動(dòng)畫開始與介紹的地方速率改變比較慢,在中間的時(shí)候加速
- AccelerateInterpolator 在動(dòng)畫開始的地方速率改變比較慢,然后開始加速
- AnticipateInterpolator 開始的時(shí)候向后然后向前甩
- AnticipateOvershootInterpolator 開始的時(shí)候向后然后向前甩一定值后返回最后的值
- BounceInterpolator 動(dòng)畫結(jié)束的時(shí)候彈起
- CycleInterpolator 動(dòng)畫循環(huán)播放特定的次數(shù),速率改變沿著正弦曲線
- DecelerateInterpolator 在動(dòng)畫開始的地方快然后慢
- LinearInterpolator 以常量速率改變
- OvershootInterpolator 向前甩一定值后再回到原來位置
- PathInterpolator 路徑插值器
總結(jié)
策略模式的重心
策略模式的重心不是如何實(shí)現(xiàn)算法,而是如何組織、調(diào)用這些算法,從而讓程序結(jié)構(gòu)更靈活,具有更好的維護(hù)性和擴(kuò)展性。
算法的平等性
策略模式一個(gè)很大的特點(diǎn)就是各個(gè)策略算法的平等性。對(duì)于一系列具體的策略算法,大家的地位是完全一樣的,正因?yàn)檫@個(gè)平等性,才能實(shí)現(xiàn)算法之間可以相互替換。所有的策略算法在實(shí)現(xiàn)上也是相互獨(dú)立的,相互之間是沒有依賴的。
所以可以這樣描述這一系列策略算法:策略算法是相同行為的不同實(shí)現(xiàn)。
運(yùn)行時(shí)策略的唯一性
運(yùn)行期間,策略模式在每一個(gè)時(shí)刻只能使用一個(gè)具體的策略實(shí)現(xiàn)對(duì)象,雖然可以動(dòng)態(tài)地在不同的策略實(shí)現(xiàn)中切換,但是同時(shí)只能使用一個(gè)。
公有的行為
經(jīng)常見到的是,所有的具體策略類都有一些公有的行為。這時(shí)候,就應(yīng)當(dāng)把這些公有的行為放到共同的抽象策略角色Strategy類里面。當(dāng)然這時(shí)候抽象策略角色必須要用Java抽象類實(shí)現(xiàn),而不能使用接口。
這其實(shí)也是典型的將代碼向繼承等級(jí)結(jié)構(gòu)的上方集中的標(biāo)準(zhǔn)做法。
end :策略模式主要就是為了分離算法和使用,是系統(tǒng)用于很好的拓展性