策略模式總結

碼魘發表於2019-01-19

1、事例的背景基礎:

做一套模擬鴨子的遊戲,遊戲中會出現各種鴨子,一邊游泳戲水,一邊呱呱叫,還有一些會飛。主要是根據鴨子這一物件的飛行行為和叫聲行為為基礎,當客戶提出不同的要求時,如何能夠在滿足不同的要求的同時,使得之前的功能正常執行,未來的功能能夠靈活適用。

2、講解的過程:分別提供了多種方案,即就是我們通常能夠想到的,繼承、介面的方案。雖然通過這些方案都可以解決當前的問題,但是對於系統的彈性和程式碼的未來適用性都不能夠滿足需求。
2.1 繼承方案:

設計一個超類Duck,包含方法quack()、swim()、fly()分別模擬鴨子的叫、游泳、飛行等行為,再包含一個抽象類display(),用於展示各個鴨子不同的外觀,讓每個鴨子子類繼承父類時實現display()。
優點:每個鴨子子類繼承父類時就同時擁有了父類的方法,可以達到程式碼複用的目的。
缺點:這樣會使某些並不適合該行為的子類也具有該行為。如果某些子類鴨子,如“橡皮鴨”,它不具備某些功能(如飛行),它就不應該擁有這個飛行的功能。當然,你可以在子類中通過@Override覆蓋這個方法。但是當各個不同的子類都需要通過覆蓋修改不同的方法時,就會非常繁瑣,而且容易出現紕漏,且這些新覆蓋的方法不能被複用。如“橡皮鴨”只會叫不會飛,“木頭鴨”不會叫也不會飛。以後每當有新的鴨子子類出現,我們都要去檢查並可能需要覆蓋這些方法,然後根據不同的需求做出不同的改進。這是不符合靈活適用的目的的。

2.2 介面方案

在超類Duck中將quack()、fly()等可變的方法用介面Quackable(),Flyable()來代替,然後在每個鴨子子類中,如果具有“飛行”或“叫”這個功能就實現“飛行“或”叫“這個介面。
優點:可以暫時解決眼前的困難。
缺點:程式碼無法複用,如果有N個子類,都具有飛行的行為,就需要重複N次程式碼。這對於開發人員和產品本身都是不符合要求的。

2.3 引入設計模式
2.3.1設計模式的原則

1、找出程式中可能需要變化的地方和不需要變化的地方,將它們獨立開來。讓系統中的某部分改變不會影響其他部分。
2、針對介面程式設計,而不是針對實現。利用多型,針對超型別程式設計,執行時根據實際狀況執行到真正的行為,不會被綁死在超型別的行為上。以前的做法是:行為來自超類的具體實現或是繼承某個介面並由子類自行實現。這兩種方法都捆綁於”實現“,無法方便地更改行為。現在我們利用介面代表每個行為,比如FlyBehavior,QuackBehavior,然後讓各個行為類實現這些介面,然後在Duck類中只要定義這個介面的例項變數即可,這樣在各個鴨子子類中如果想擁有某種特定的行為,只要用這個介面例項變數去引用具體的行為類即可。
3、多用組合,少用繼承。飛行和叫這兩種不同的行為,我們分別為其建立兩組不同的行為類,然後在Duck類中通過介面例項變數結合起來,這就是”組合“。使得系統具有很大的彈性。

2.3.2根據教案寫出的程式碼事例:

1、介面FlyBehavior
public interface FlyBehavior {
    public void fly();
 }
2、介面QuackBehavior
public interface QuackBehavior {
 public void quack();
 }
3、Fly行為的一個實現——FlyNoWay
public class FlyNoWay implements FlyBehavior{
    @Override
        public void fly() {
        System.out.println("I can not fly.");
    }
}
 4、public class FlyWithWings implements FlyBehavior {
     @Override
        public void fly() {
         System.out.println("I can fly!");
     }
 }
5、Fly行為的又另一個實現——FlyRocketPowered 
 public class FlyRocketPowered implements FlyBehavior{
     @Override
     public void fly() {
        System.out.println("I am flying with a rocket.");
     }
    }
6、父類Duck
public abstract class Duck {
FlyBehavior flyBehavior;
QuackBehavior quackBehavior;
public abstract void display();
public void performFly(){
    flyBehavior.fly();
}
public void performQuack(){
    quackBehavior.quack();
}
public void setFlyBehavior(FlyBehavior fb){
    this.flyBehavior = fb;
}
public void setQuackBehavior(QuackBehavior qb){
    this.quackBehavior=qb;
}
}
Duck的一個子類——綠頭鴨MallardDuck
public class MallardDuck extends Duck{
public MallardDuck() {
    flyBehavior = new FlyWithWings();
    quackBehavior = new QuackWithGuaGua();
}

@Override
public void display() {
    System.out.println("I am a MallardDuck.");
    
}
}
Duck的另一個子類——模型鴨ModelDuck
public class ModelDuck extends Duck {
public ModelDuck() {
    flyBehavior = new FlyNoWay();
    quackBehavior = new QuackNoWay();
}

@Override
public void display() {
    System.out.println("I am a ModelDuck.");        
}
}
main方法:MiniDuckSimulator
public class MiniDuckSimulator {

public static void main(String[] args) {
    Duck mallardDuck = new MallardDuck();
    mallardDuck.display();
    mallardDuck.performFly();
    mallardDuck.performQuack();
    Duck modelDuck = new ModelDuck();
    modelDuck.display();
    modelDuck.performFly();
    modelDuck.performQuack();
    modelDuck.setFlyBehavior(new FlyRocketPowered());
    modelDuck.performFly();
}

}

3、總結。

 策略模式定義了演算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓演算法的變化獨立於使用演算法的客戶,這是書裡給出的策略模式的定義。個人的理解:就是我們要把一些物件的可變的行為抽象出來,單獨進行封裝,用一組行為類(演算法族)來實現該特定的介面,這樣任何類(如Duck)如果想擁有這些演算法族中的某個演算法,都可以通過定義介面例項變數而擁有該整個演算法族,在子類中再對該變數進行賦值。像這個例子裡,通過定義了FlyBehavior,這樣以後任何想有飛行行為的類如飛機,都可以呼叫這個介面及實現它的各個飛行類。且飛行行為的變化可以通過增加新的飛行類來實現,不會對其他部分(如quack()、display()等行為,亦或是已擁有某些特定飛行行為的物件)造成任何影響,即”演算法的變化獨立於使用演算法的客戶“。而且各個飛行行為之間也可以互相替換。即 setFlyBehavior(Flybehavior fb)。通過這種方式,能夠使程式碼的複用性很強,又可以解決各種多變的需求。
通過這樣的學習一遍策略模式,雖然對於策略模式有了一定的理解,但是想要靈活的運用在開發的過程中,還是需要多多的練習和體會。

注:本文的事例和解析思路來源於內容來源於《Heard First 設計模式》一書

相關文章