設計模式之策略模式

設計模式之策略模式
     定義:定義了算法族,分別封裝起來,讓它們之間可以相互替換,此模式讓算法的變法獨立於使用算法的客戶。
     關於模式的學習本來就是比較頭痛的事情,如果但看看理論。就算你知道了它應用的場景。在實際的應用中,你也很難的應用到你的項目中。理解容易,實際的應用更難。
      接下來我要通過一個實例來講解這個設計模式。通過不斷的需求變化,來真正的體現設計模式帶來的好處,我們使用一些設計模式,就是讓你的程序可以應付客戶的不斷的需求變化。我們知道,在我們實際的開發中,難的不是技術上的問題,而是客戶隨着開發的進行,他們的需求的不斷的變化,這是最頭痛的,如果你的程序不能對付客戶的變化的話,你就會付出更多的代價。爲了讓我們的程序可以健壯,重用,可擴展。利用設計模式的思想去開發編程,可以解決上面的幾個問題。這並不是唯一的辦法,但是比較好的辦法。
      我們的要求是設計一個模擬鴨子游戲的應用程序。(注:這個應用是在Head First 設計模式 一書中的例子)。遊戲中會出現各種鴨子,一邊游泳戲水,一邊呱呱叫。對於這樣的要求,我們首先會運用OO技術,並利用繼承的思想,設計一個鴨子的超類(Superclass),並讓各種鴨子繼承這個超類。
public class Duck{
    public void quack(){  //呱呱叫
       System.out.println("呱呱叫");
     }
    public void swim(){   //游泳
           System.out.println(" 游泳");
   }
   public  abstract void display(); /*因爲外觀不一樣,讓子類自己去決定了。*/
}
 對於它的子類只需簡單的繼承就可以了,並實現自己的display()方法。
//野鴨
public class MallardDuck extends Duck{
    public void display(){
         System.out.println("野鴨的顏色...");
  }
}
//紅頭鴨
public class RedheadDuck extends Duck{
    public void display(){
         System.out.println("紅頭鴨的顏色...");
  }
}
這裏可能還會有其他顏色的鴨子。通過這個簡單的繼承我們滿足了客戶的要求。
  不幸的是,現在客戶又提出了新的需求,想讓鴨子飛起來。這個對於我們OO程序員,在簡單不過了,在超類中在加一個方法就可以了。
public class Duck{
    public void quack(){  //呱呱叫
       System.out.println("呱呱叫");
     }
    public void swim(){   //游泳
           System.out.println(" 游泳");
   }
   public  abstract void display(); /*因爲外觀不一樣,讓子類自己去決定了。*/
  public void fly(){
       System.out.println("飛吧!鴨子");
 }
}
在子類中只需簡單的覆蓋。
//殘廢鴨
public class DisabledDuck extends Duck{
    public void display(){
         System.out.println("殘廢鴨的顏色...");
  }
  public void fly(){
   //覆蓋,變成什麼事都不做。
 }
}
其它會飛的鴨子不用覆蓋。
這樣所有的繼承這個超類的鴨子都會fly了。但是問題又出來了,客戶又提出有的鴨子會飛,有的不能飛。客戶就是那樣的讓我們頭痛啊,他們的需求會隨着我們的開發而改變的。
這時我們需要做的是對會飛的鴨子重寫fly方法,對於不能飛的也重寫,但在方法裏什麼也不寫,只是一個空方法,沒有任何實現。只是簡單覆蓋超類中的fly方法。這樣可以滿足暫時的需求了,但對我們的維護帶來了麻煩。對於一些不會叫的,也不會飛的鴨子的子類中,夾雜了一些沒有意義的代碼,就是對一些超類方法的覆蓋。  
     對於上面的設計,你可能發現一些弊端,如果超類有新的特性,子類都必須變動,這是我們開發最不喜歡看到的,一個類變讓另一個類也跟着變,這有點不符合OO設計了。這樣很顯然的耦合了一起。
      這時,我們會馬上想到運用接口來消除這種弊端。我們把容易引起變法的部分提取出來並封裝之,來應付以後的變法。雖然代碼量加大了,但可用性提高了,耦合度也降低了。同時也具有很強的擴展性,也真正的利用了OO設計思想。
   我們把Duck中的fly方法和quack提取出來。
   public interface Flyable{
     public void fly();
 }
  public interface Quackable{
    public void quack();
 }
 最後Duck的設計成爲:

public class Duck{
    public void swim(){   //游泳
           System.out.println(" 游泳");
   }
   public  abstract void display(); /*因爲外觀不一樣,讓子類自 己去決定了。*/
}
 而MallardDuck,RedheadDuck,DisabledDuck 就可以寫成爲:
//野鴨
public class MallardDuck extends Duck  implements Flyable,Quackable{
    public void display(){
         System.out.println("野鴨的顏色...");
  }
  public void fly(){
   //實現該方法
 }
  public void quack(){
   //實現該方法
 }
}
//紅頭鴨
public class RedheadDuck extends Duck implements Flyable,Quackable{
    public void display(){
         System.out.println("紅頭鴨的顏色...");
  }
  public void fly(){
   //實現該方法
 }
  public void quack(){
   //實現該方法
 }
}
//殘廢鴨 只實現Quackable(能叫不能飛)
public class DisabledDuck extends Duck implements Quackable{
    public void display(){
         System.out.println("殘廢鴨的顏色...");
  }
  public void quack(){
   //實現該方法
 }
}

   這樣已設計,我們的程序就降低了它們之間的耦合。
   現在我們知道使用繼承並不能很好的解決問題,因爲鴨子的行爲在子類中不斷的變化,並且讓所用的子類都有這些行爲是不恰當的。Flyable和Quackable接口一開始似乎還挺不錯的,解決了問題(只有會飛到鴨子才實現 Flyable),但是Java接口不具有實現代碼,所以實現接口無法達到代碼的複用。這時我們有一個設計原則:找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。
    現在我們根據這個設計原則,我們上面的設計還存在問題。我們因該如何解決呢?
    現在,爲了要分開“變化和不變化的部分”,我們準備建立兩組類(完全遠離Duck類),一個是"fly"相關的,另一個是“quack”相關的,每一組類將實現各自的動作。比方說,我們可能有一個類實現“呱呱叫”,另一個類實現“吱吱叫”,還有一個類實現“安靜”。
   我們如何設計實現飛行和呱呱叫的行爲的類呢?我們爲了讓我們的程序有彈性,減小耦合。我們又必須遵循第二個設計原則:針對接口編程,而不是針對實現編程。
  看看我們具體的實現吧,實現勝於理論。
  首先寫兩個接口。FlyBehavior(飛行行爲)和QuackBehavior(叫的行爲).
 public interface FlyBehavior{
    public void fly();    
}
public interface QuackBehavior{
    public void quack();
}
我們在定義一些針對FlyBehavior的具體實現。
public class FlyWithWings implements FlyBehavior{
   public void  fly(){
    //實現了所有有翅膀的鴨子飛行行爲。
  }
}

public class FlyNoWay implements FlyBehavior{
   public void  fly(){
     //什麼都不做,不會飛
  }
}

針對QuackBehavior的幾種具體實現。

public class Quack implements QuackBehavior{
   public void quack(){
     //實現呱呱叫的鴨子
 }
}
public class Squeak implements QuackBehavior{
   public void quack(){
     //實現吱吱叫的鴨子
 }
}
public class MuteQuack implements QuackBehavior{
   public void quack(){
     //什麼都不做,不會叫
 }
}
  這樣的設計,可以讓飛行和呱呱叫的動作被其他的對象複用,因爲這些行爲已經與鴨子類無關了。而我們增加一些新的行爲,不會影響到既有的行爲類,也不會影響“使用”到飛行行爲的鴨子類。
    最後我們看看Duck 如何設計。
    public class Duck{
     FlyBehavior flyBehavior;//接口
     QuackBehavior quackBehavior;//接口
      public Duck(){}
      public abstract void display();
      public void swim(){
       //實現游泳的行爲
       }
      public void performFly(){
           flyBehavior.fly();//這時鴨子對象不親自處理飛行行爲,而是委託給flyBehavior引用的對象。
    }
    public void performQuack(){
         quackBehavior.quack();();//這時鴨子對象不親自處理叫的行爲,而是委託給quackBehavior引用的對象。
   }
}
 看看MallardDuck如何實現。
public class MallardDuck extends Duck{
      public MallardDuck() {
       flyBehavior = new FlyWithWings ();
       quackBehavior = new Quack();
     //因爲MallardDuck 繼承了Duck,所有具有flyBehavior 與quackBehavior 實例變量}
   public void display(){
    //實現
  }
}
這樣就滿足了即可以飛,又可以叫,同時展現自己的顏色了。
  這樣的設計我們可以看到是把flyBehavior ,quackBehavior 的實例化寫在子類了。我們還可以動態的來決定。
 我們只需在Duck中加上兩個方法。
 public class Duck{

     FlyBehavior flyBehavior;//接口
     QuackBehavior quackBehavior;//接口
     public void setFlyBehavior(FlyBehavior flyBehavior){
           this.flyBehavior = flyBehavior;
    }
   public void setQuackBehavior(QuackBehavior quackBehavior  {
           this.quackBehavior= quackBehavior;
    }
}
   大家看到這樣的方法應該知道它是利用依賴注入的思想,動態的來改變鴨子的行爲。
 在測試的類中,我們可以這樣寫。
 public class Test{
     public static void main(String[] args){
        //一般的用法:
         Duck mallard = new MallardDuck();
         mallard.performFly();
         mallard .perforeQuack();
       //動態的改變
       mallard.setFlyBehavior(new FlyRocketPowered);//具有火箭動力的飛行能力
     mallard.performFly();
     }
 }
     這樣的用法其實就是Spring 中的一個核心機制,Ioc依賴注入思想。
      我們講了這麼些,其實就是策略模式的應用。我們寫了這麼多,就是爲了讓你更加明白這個設計模式的應用場景。
      策略模式:定義了算法族(飛行行爲中的各種實現<看成一族算法>和呱呱叫行爲中的各種實現<看成一族算法>),分別分裝起來,讓它們之間可以相互調用,此模式讓算法的變法(就是飛行行爲和呱呱叫的行爲)獨立於使用算法的客戶。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章