天天看點

設計模式之政策模式

 定義:定義了算法族,分别封裝起來,讓它們之間可以互相替換,此模式讓算法的變法獨立于使用算法的客戶。

      關于模式的學習本來就是比較頭痛的事情,如果但看看理論。就算你知道了它應用的場景。在實際的應用中,你也很難的應用到你的項目中。了解容易,實際的應用更難。

       接下來我要通過一個執行個體來講解這個設計模式。通過不斷的需求變化,來真正的展現設計模式帶來的好處,我們使用一些設計模式,就是讓你的程式可以應付客戶的不斷的需求變化。我們知道,在我們實際的開發中,難的不是技術上的問題,而是客戶随着開發的進行,他們的需求的不斷的變化,這是最頭痛的,如果你的程式不能對付客戶的變化的話,你就會付出更多的代價。為了讓我們的程式可以健壯,重用,可擴充。利用設計模式的思想去開發程式設計,可以解決上面的幾個問題。這并不是唯一的辦法,但是比較好的辦法。

       我們的要求是設計一個模拟鴨子遊戲的應用程式。(注:這個應用是在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{

          System.out.println("紅頭鴨的顔色...");

這裡可能還會有其他顔色的鴨子。通過這個簡單的繼承我們滿足了客戶的要求。

   不幸的是,現在客戶又提出了新的需求,想讓鴨子飛起來。這個對于我們OO程式員,在簡單不過了,在超類中在加一個方法就可以了。

   public void fly(){

        System.out.println("飛吧!鴨子"); 

  }

在子類中隻需簡單的覆寫。

//殘廢鴨

 public class DisabledDuck extends Duck{

          System.out.println("殘廢鴨的顔色...");

    //覆寫,變成什麼事都不做。 

其它會飛的鴨子不用覆寫。

 這樣所有的繼承這個超類的鴨子都會fly了。但是問題又出來了,客戶又提出有的鴨子會飛,有的不能飛。客戶就是那樣的讓我們頭痛啊,他們的需求會随着我們的開發而改變的。

 這時我們需要做的是對會飛的鴨子重寫fly方法,對于不能飛的也重寫,但在方法裡什麼也不寫,隻是一個空方法,沒有任何實作。隻是簡單覆寫超類中的fly方法。這樣可以滿足暫時的需求了,但對我們的維護帶來了麻煩。對于一些不會叫的,也不會飛的鴨子的子類中,夾雜了一些沒有意義的代碼,就是對一些超類方法的覆寫。  

      對于上面的設計,你可能發現一些弊端,如果超類有新的特性,子類都必須變動,這是我們開發最不喜歡看到的,一個類變讓另一個類也跟着變,這有點不符合OO設計了。這樣很顯然的耦合了一起。

       這時,我們會馬上想到運用接口來消除這種弊端。我們把容易引起變法的部分提取出來并封裝之,來應付以後的變法。雖然代碼量加大了,但可用性提高了,耦合度也降低了。同時也具有很強的擴充性,也真正的利用了OO設計思想。

    我們把Duck中的fly方法和quack提取出來。

    public interface Flyable{

      public void fly(); 

   public interface Quackable{

     public void quack();

  最後Duck的設計成為:

    public  abstract void display(); /*因為外觀不一樣,讓子類自 己去決定了。*/

  而MallardDuck,RedheadDuck,DisabledDuck 就可以寫成為:

 public class MallardDuck extends Duck  implements Flyable,Quackable{

    //實作該方法

   public void quack(){

 public class RedheadDuck extends Duck implements Flyable,Quackable{

//殘廢鴨 隻實作Quackable(能叫不能飛)

 public class DisabledDuck extends Duck implements Quackable{

    這樣已設計,我們的程式就降低了它們之間的耦合。

    現在我們知道使用繼承并不能很好的解決問題,因為鴨子的行為在子類中不斷的變化,并且讓所用的子類都有這些行為是不恰當的。Flyable和Quackable接口一開始似乎還挺不錯的,解決了問題(隻有會飛到鴨子才實作 Flyable),但是Java接口不具有實作代碼,是以實作接口無法達到代碼的複用。這時我們有一個設計原則:找出應用中可能需要變化之處,把它們獨立出來,不要和那些不需要變化的代碼混在一起。 

     現在我們根據這個設計原則,我們上面的設計還存在問題。我們因該如何解決呢?

     現在,為了要分開“變化和不變化的部分”,我們準備建立兩組類(完全遠離Duck類),一個是"fly"相關的,另一個是“quack”相關的,每一組類将實作各自的動作。比方說,我們可能有一個類實作“呱呱叫”,另一個類實作“吱吱叫”,還有一個類實作“安靜”。

    我們如何設計實作飛行和呱呱叫的行為的類呢?我們為了讓我們的程式有彈性,減小耦合。我們又必須遵循第二個設計原則:針對接口程式設計,而不是針對實作程式設計。

   看看我們具體的實作吧,實作勝于理論。

   首先寫兩個接口。FlyBehavior(飛行行為)和QuackBehavior(叫的行為).

  public interface FlyBehavior{

     public void fly();     

 public interface QuackBehavior{

 我們在定義一些針對FlyBehavior的具體實作。

 public class FlyWithWings implements FlyBehavior{

    public void  fly(){

     //實作了所有有翅膀的鴨子飛行行為。

 public class FlyNoWay implements FlyBehavior{

      //什麼都不做,不會飛

    }

 }   

針對QuackBehavior的幾種具體實作。

public class Quack implements QuackBehavior{

    public void quack(){

      //實作呱呱叫的鴨子

public class Squeak implements QuackBehavior{

      //實作吱吱叫的鴨子 

public class MuteQuack implements QuackBehavior{

      //什麼都不做,不會叫

   這樣的設計,可以讓飛行和呱呱叫的動作被其他的對象複用,因為這些行為已經與鴨子類無關了。而我們增加一些新的行為,不會影響到既有的行為類,也不會影響“使用”到飛行行為的鴨子類。

     最後我們看看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 MallardDuck {

        flyBehavior = new FlyWithWings ();

        quackBehavior = new Quack(); 

      //因為MallardDuck 繼承了Duck,所有具有flyBehavior 與quackBehavior 執行個體變量}

    public void display(){

     //實作

 這樣就滿足了即可以飛,又可以叫,同時展現自己的顔色了。

   這樣的設計我們可以看到是把flyBehavior ,quackBehavior 的執行個體化寫在子類了。我們還可以動态的來決定。

  我們隻需在Duck中加上兩個方法。

  public class Duck{

      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依賴注入思想。

       我們講了這麼些,其實就是政策模式的應用。我們寫了這麼多,就是為了讓你更加明白這個設計模式的應用場景。

       政策模式:定義了算法族(飛行行為中的各種實作<看成一族算法>和呱呱叫行為中的各種實作<看成一族算法>),分别分裝起來,讓它們之間可以互相調用,此模式讓算法的變法(就是飛行行為和呱呱叫的行為)獨立于使用算法的客戶。

本文轉自 weijie@java 51CTO部落格,原文連結:http://blog.51cto.com/weijie/74807,如需轉載請自行聯系原作者