22】之策略模式(Strategy)
1 模式簡介
在策略模式中,一個類的行為或其演算法可以在執行時改變。策略模式定義了一系列演算法,把它們一個個封裝起來,並且使它們可以互相替換。
策略模式的優點:
1) 演算法可以自由切換;
2) 避免使用多重條件判斷;
3) 擴充套件性良好。
策略模式的缺點:
1) 演算法可以自由切換;
2) 避免使用多重條件判斷;
3) 擴充套件性良好。
策略模式的適用場景:
1) 當一個系統中有許多類,它們之間的區別僅在於它們的行為,希望動態地讓一個物件在許多行為中選擇一種行為時;
2) 當一個系統需要動態地在幾種演算法中選擇一種時;
3) 當一個物件有很多的行為,不想使用多重的條件選擇語句來選擇使用哪個行為時。
2 案例
在這個例子中,我們來模擬一個遊戲:這個遊戲中有各種鴨子,它們會飛,也會叫,但它們飛和叫的方式不同,如紅頭鴨是呱呱叫的,而橡皮鴨是吱吱叫的;紅頭鴨是用翅膀飛的,而橡皮鴨是不會飛的。我們希望用JAVA程式語言結合策略模式來完成這個功能。
2.1 使用繼承
鴨子雖然有不同的種類,但是也有一定的相同之處,所以我們可以從鴨子中提取一個父類Duck,讓不同種類的鴨子類繼承自父類,將所有鴨子共有的屬性和行為放到父類中。這種思想對應的類圖如下:這樣做看似沒有錯誤,但實際上存在以下兩個方面的錯誤:
1、子類的可用行為可能遠大於我們的期待範圍。把所有方法和屬性都定義在Duck父類中,再由所有子類去繼承這個父類,那麼所有的子類就都具備父類的方法和屬性。就拿這個遊戲的例子來說,如果加入一個方法fly(),那麼就算是橡皮鴨(RubberDuck)類也一定會具備fly()的行為。
2、這種方法並不能有效的解決程式碼重複問題。拿上一條中的fly()方法來說,既然不同的鴨子具有不同的飛行方式,則fly()方法的方法體一定是寫在子類中;而不同種類的鴨子的飛行方式也可能是相同的,所以我們就可能不得不在多個鴨子中使用相同的fly()方法,而在另外一些鴨子中使用另一種fly()方法。
2.2 增加介面
我們可以把Duck父類中的fly()方法提取出來定義成介面,稱為Flyable,其中定義一個抽象方法fly(),讓可以飛的Duck子類實現這個介面並實現fly()方法。各個類中的行為細節及類間關係如下圖所示。
使用介面有效的解決了上面的第一個問題,即可以有效的控制各個子類的行為範圍,不會出現“子類具有父類的所有方法”的情況了。但是,JAVA中的介面中不具備程式碼實現功能,所以這樣做並沒有解決程式碼重複的問題。
2.3 解決問題
我們需要找出應用中可能需要變化的地方,把它們獨立出來,不要和那些不需要變化的程式碼混在一起,即把會變化的部分取出並封裝起來,一邊以後可以輕易的改動或擴充套件此部分,而不影響不需要變化的部分。
在上面的兩次嘗試(使用繼承、增加介面)中,我們都只是把共有的屬性和行為抽出來封裝成父類或介面,但在這裡,我們要儘量兩次封裝,即在業務類上面還有兩層封裝類。對於一種行為,我們首先將這種行為抽取出來作為一個總的行為介面,然後再在這個介面下面定義多個不同的實現類,最後再在業務類中呼叫父類完成業務。簡單的說,在上面兩種嘗試中,我們只是定義了兩個互不相關的行為,而在這種方法中,我們定義的是一組有關聯的行為。
拿我們這個例子中的fly()方法舉例,飛,可能是用翅膀飛(FlyWithWings),也可能是坐火箭飛(FlyRocketPower)。因此,我們定義一個總的介面FlyBehavior,在這個介面中定義一個抽象方法fly()。再定義兩個實現FlyBehavior介面的類FlyWithWings和FlyRocketPower,這兩個類實現FlyBehavior介面,並實現介面中的fly()方法。另外,我們在Duck父類中定義一個FlyBehavior的介面變數,在生成鴨子實體的時候,給其FlyBehavior賦值,以此決定鴨子的飛行方式。我們還可以在Duck父類中定義一個設定飛行方式的方法setFlyBehavior(),來動態的改變鴨子的飛行方式。以下是類圖:
以下貼出對這個問題的解決方案的程式碼:
飛行介面FlyBehavior中的程式碼:public interface FlyBehavior {
// 飛行的抽象方法
void fly();
}
使用翅膀飛行類FlyWithWings中的程式碼:
public class FlyWithWings implements FlyBehavior {
@Override
public void fly() {
System.out.println("使用翅膀飛行");
}
}
其實還有不會飛的類FlyNoWay,這裡就不貼出來了。
叫的介面QuackBehavior中的程式碼:
public interface QuackBehaviro {
// 叫的抽象方法
void quack();
}
呱呱叫的類Quack中的程式碼:
public class Quack implements QuackBehaviro {
@Override
public void quack() {
System.out.println("呱呱叫");
}
}
其實還有吱吱叫的類Squeak,這裡就不貼出來了。
鴨子的父類Duck中的程式碼:
public class Duck {
protected FlyBehavior flyBehavior;
protected QuackBehaviro quackBehaviro;
public void performFly() {
flyBehavior.fly();
}
public void performQuack() {
quackBehaviro.quack();
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehaviro(QuackBehaviro quackBehaviro) {
this.quackBehaviro = quackBehaviro;
}
}
鴨子的一個子類紅頭鴨RedHeadDuck中的程式碼:
public class RedHeadDuck extends Duck {
public RedHeadDuck() {
System.out.println("只是一隻紅頭鴨");
super.flyBehavior = new FlyWithWings();
super.quackBehaviro = new Quack();
}
}
鴨子的另一個子類橡皮呀RubberDuck中的程式碼:
public class RubberDuck extends Duck {
public RubberDuck() {
System.out.println("這是一隻橡皮鴨");
super.flyBehavior = new FlyNoWay();
super.quackBehaviro = new Squeak();
}
}
測試類Test中的程式碼:
public class Test {
public static void main(String[] args) {
Duck redHeadDuck = new RedHeadDuck();
redHeadDuck.performFly();
redHeadDuck.performQuack();
System.out.println();
Duck rubberDuck = new RubberDuck();
rubberDuck.performFly();
rubberDuck.performQuack();
}
}
執行結果如下圖所示: