策略模式—動態更改演算法

來源:互聯網
上載者:User

      策略模式是設計模式中很重要的一種,它的主要意圖就是:定義了演算法族,分別封裝起來,讓它們之間可以互相替換。它讓演算法的變化可以獨立於使用演算法的客戶,使得我們可以隨時更改它們而不影響用戶端的代碼,而用戶端可以自由選擇不同的演算法執行。

       要想瞭解策略模式,我們就要理解幾個重要概念:

      1.什麼是策略?      2.什麼是演算法?      3.演算法可替換的條件?        所謂的策略就是指在給定的輸入條件下,實現某個目標的計劃或方案,而演算法是一個定義好的過程,能夠根據一組輸入產生一個輸出。對於這兩者可以這樣理解:策略是一組可替換的演算法。       能夠相互替換的演算法必須具有相同的特點,就是它們處理的對象的來源一樣,去向也一樣,至於對象的類型,不要求相同。       明白了策略和演算法的區別,我們接下來就要討論策略模式的幾個重要方面:             1.使用原則模式的原因?      2.策略模式的實現?      3.策略模式的優缺點?       為什麼我們要使用原則模式呢?想想這樣的情況,我們負責一個基地戰鬥機資料的記錄,戰鬥機有各種型號和效能,效能中的武器和飛行方式又是各種各樣,但是我們提供的介面只有兩個方法:fly()和attack()。想要在這兩個方法中展現不同戰鬥機的效能,我們可能會用繼承。        首先,我們會定義一個抽象類別:Plane,這個抽象類別的大概樣子像是下面這樣:
public abstract class Plane{   private abstract void fly();   private abstract void attack(); }

       接著我們就開始定義一些具體型號的戰鬥機:

      public class RocketPlane extends Plane{             private void fly(){                 System.out.println("This plane fly with Rocket");            }            private void attack(){                 System.out.println("This plane attack with fire");            }       }              public class WindPlane extends Plane{           private void fly(){                  System.out.println("This plane fly with wind");           }           private void attack(){                  System.out.println("This plane attack with Rocket");           }      }

          然後就在我們的程式中這樣寫:

 public static void main(String[] args){            RocketPlane plane1 = new RocketPlane();            WindPlane plane2 = new WindPlane();                       showFunctionOfPlane(plane1);            showFunctionOfPlane(plane2);       }            private void showFunctionOfPlane(Plane plane){             plane.fly();             plane.attack();      }

        使用繼承可以解決這個問題,但是,繼承也有它自己的問題。繼承最大的問題就是,基類的改變會傳給所有的子類,這是我們類設計者不想看到的。那麼,不使用繼承不就可以了?介面,就是我們這種情況下最好的替代方案。

       使用介面,我們的代碼就可以更加靈活。       介面是一個好東西,但如何使用,也是一個重要的問題,就是我們該讓什麼成為介面?如果我們這裡將戰鬥機這個抽象作為介面,像是這樣:      

    public Interface Plane{         void fly();         void attack();     }

       這樣就將具體的實現交給實作類別,從而避免我們上面的問題。確實如此,但不同型號的戰鬥機,就算外觀差距太大,基本的東西都是不變的,像是重量這些基本的屬性,至少在很長的一段時間都不會發生變化,如果用介面的話,我們就不能設定一些共同的屬性和方法,當然我們可以將這樣的東西交給實作類別來實現,這樣,代碼重複的程度太可怕了!

       抽象類別還是有抽象類別的好處,介面依然不能完全代替,就像上面的例子。介面在很大程度上都被人濫用了,因為它是一個非常好用的東西,尤其是多態的使用。但是類的設計應該貼近現實生活,就像上面戰鬥機的例子,我們是對戰鬥機這樣的具體東西進行抽象,而且代碼應該能體現程式員對待一個問題的思維,並不僅僅是交給電腦自己處理的位元組碼。濫用介面本身就是對這種原則的破壞,因為很多人都不清楚介面的真正意義。       使用抽象類別是為了表達“is-a”關係,而使用介面是為了表達"has-a"關係。繼承自一個抽象類別,子類本身就是抽象類別的一個特例,在分類上它屬於抽象類別,但是實現一個介面,並不能說,我們的實作類別就是一個介面,準確的說法就是我們的實作類別具有該介面定義的行為,當然,對於類型識別來說,介面和抽象類別是沒有什麼區別的,都是一個事物的抽象。介面的真正意義是一組行為協議,規定我們的實作類別應該具有的行為。也許有些人會說,這樣是“is-like-a"關係,這樣的說法其實搞錯了"is-like-a"關係,"is-like-a"關係本身也屬於繼承的一種關係,傳統意義上的繼承應該是完全繼承,就是不添加新的功能,只是覆寫我們基類的方法,這樣子類就可以向上轉型為基類而不會出錯,但是,現實就是子類會有它們自己的行為,會有自己特有的屬性和行為,使得它們無法向上轉型,這就是"is-like-a"關係。        理解好介面和抽象類別的邏輯意義,我們在設計的時候就能根據現實生活來決定到底應該採用什麼樣的抽象。上面的例子,我們依然使用抽象類別,因為我們需要一個地方來存放所有戰鬥機都具有的屬性和行為,抽象類別是一個非常好的選擇。接著,我們將會改動的行為抽取出來作為介面。如何判斷一個行為是否應該抽取出來,我們就看:如果我們對該行為進行修改,相應的其他代碼是否也要進行修改,如果需要,說明這個行為是一個變化的行為因素。這裡我們就抽取出飛行和攻擊這兩個行為。        我們現在抽取出飛行和攻擊這兩個介面:
 public Interface FlyAble{     void fly();  }           public Interface AttackAble{      void attack();  }
         這裡我們就可能犯一個錯誤,像是這樣:
         public class RocketPlane extends Plane implements FlyAble, AttackAble{               void fly(){
System.out.println("This plane fly with Rocket");
} void attack(){
System.out.println("This plane attack with fire");
} } public class WindPlane extends Plane implements FlyAble, AttackAble{ void fly(){
System.out.println("This plane fly with wind"); 
} void attack(){
System.out.println("This plane attack with Rocket"); 
} }

         為什麼會這樣寫?很簡單,因為我們可能有些飛機根本不具有飛行能力,像是這樣:

  public class NotFlyPlane extends Plane implements AttackAble{       void attack(){
System.out.println("This Plane attack with wind");
} }

        但是,根本不需要我們的子類實現這些介面,介面更大的意義是對象組合,這樣根本就失去了介面的優點。要想利用介面的這些優點,我們可以這樣建立這兩個介面的實作類別組,像是這樣:

           public class FlyWithRocket implements FlyAble{               void fly(){
System.out.println("This plane fly with rocket");
} }
public class AttackWithRocket implements AttackAble{ void attack(){
System.out.println("This Plane attack with rocket");
} }

public class FlyWithWind implements FlyAble{
void fly(){
System.out.println("This plane fly with wind");
}
}

public class AttackWithFire implements AttackAble{
void attack(){
System.out.println("This plane attack with fire");
}
}

public class NotFly implements FlyAble{
void fly(){
System.out.println("This plane can't fly");
}
}

       然後再在我們的代碼中使用這些實作類別:

 public abstract class Plane(){
protected FlyAble mFly;
protected AttackAble mAttack;

protected abstract void description();

protected void functionTest(){
mFly.fly();
mAttack.attack();
}
}

public class RocketPlane extends Plane{ RocketPlane(){
super.mFly = new FlyWithRocket(); super.mAttack = new AttackWithFire();
}
protected void description(){
System.out.println("I am a RocketPlane");
}
}

public class test{
public static void main(String[] args){
RocketPlane plane = new RocketPlane();
plane.functionTest();
}
}

       這就是使用對象組合的方式,但是這樣的方式還不夠優雅。這時,策略模式就正式登場了,因為它就是處理對象組合的一種模式。

       我們的抽象類別可以這樣修改:

public abstract class Plane {  

    private FlyAble mFly; 

    private AttackAble mAttack;

    Plane() {  }

    Plane(FlyAble fly, AttackAble attack) {   

      this.mFly = fly;  

      this.mAttack = attack;

    }

    protected abstract void description();

    protected void setFly(FlyAble fly) {   

       this.mFly = fly;

    }

    protected void setAttack(AttackAble attack) {   

       this.mAttack = attack;  

    }

    protected void testFunction() {

      description();   

      mFly.fly();  

      mAttack.attack();  

   }

}

           接著是我們的子類和測試類別:

public class RocketPlane extends Plane {

    RocketPlane() { }

    RocketPlane(FlyAble fly, AttackAble attack) {   

      super(fly, attack);

    }

   @Override  

   protected void description() {   

      System.out.println("I am a RocketPlane");

   }

}

   public class test{

      public static void main(String[] args){

          RocketPlane plane = new RocketPlane(new FlyWithRocket(), new AttackWithFire());

          plane.testFunction();

      }

   }

         如果戰鬥機以後的飛行能力發生變化,我們可以動態更改它的行為,像是這樣:
plane.setFly(new FlyWithWind());
       這樣,它就從火箭噴射變成靠機翼飛行!!而且我們客戶可以隨時更換飛行能力,只要他喜歡,甚至可以是沒有飛行能力。       以上就是策略模式的標準用法,它完全體現了策略模式的意圖。但是,繼承的問題依然存在,基類的變化依然會傳給子類,所以,我們必須保證,基類中的非抽象部分是在很長一段時間內都不會發生變化的。      策略模式的組成其實還包括一個環境角色類(Context),它擁有策略抽象的引用,可以隨時更換策略和執行策略。上面的例子中,抽象類別似乎就是一個環境角色類,但真正決定策略的動態執行的方式是子類,子類才是真正的Context。環境角色類的真正意義是策略的具體應用環境,毫無疑問,就是具體的子類。     策略模式還可以與委託掛鈎。     還是上面的例子,我們這次建立一個委託類:
public class FunctionTest{     private Plane mPlane;          FunctionTest(Plane plane){          this.mPlane = plane;      }     void functionTest(){          plane.testFunction();      }}

public class test{
public static void main(String[] args){
RocketPlane plane = new RocketPlane(new FlyWithRocket(), new AttackWithFire());
FunctionTest test = new FunctionTest(plane);
test.functionTest();
}
}

      使用委託類到底有什麼好處? 委託類提供的就是一個間接層,我們不需要知道有關於戰鬥機的具體細節,我們只知道,使用functionTest()就可以讓我們的戰鬥機飛起來,攻擊敵人。這就是封裝,客戶只知道調用委託類提供的方法就可以,就算戰鬥機的內部構造發生變化,像是testFunction()變成function(),又和我們客戶代碼有什麼關係呢?

      到了這裡,策略模式的基本內容已經講完了,通過使用介面來實現對象組合,我們就可以充分的做到代碼複用。實現策略模式真的需要繼承嗎?不一定,因為策略模式只是為了封裝一組演算法族,然後實現演算法的替換而已,只要達到這個目的都可以說是策略模式。       介紹完策略模式後,最後的部分就是針對我們上面提出的三個問題進行解答:       1.使用原則模式的原因?          原因與意圖一樣。有些書本可能會講,策略模式就是為了處理if...else if...else這種大量的條件陳述式塊,但千萬不要因為這樣的提示而隨便使用原則模式,雖然這裡的確使用了演算法的替換,但是,我們並不一定能完全去除掉這些條件判斷,肯定的是我們可以將這些代碼封裝起來,尤其是那些類似的演算法中的重複代碼。但這並不是策略模式該做的事情,它更大的作用就是可以在運行時動態改變演算法,而且可以將演算法的實現交給具體的子類實現,我們類的設計者就不需要為以後的變化考慮太多,只要為以後的變化留下改動的空間就可以了。        2.策略模式的實現?           策略模式的實現是各種各樣,但是基本的思想是不會變的,不同的語言,不同的情境都可以使用不同實現的策略模式。        3.策略模式的優缺點?           優點就是它的意圖,缺點呢?這個就難講了,因為大部分情況下模式的缺點都是不正確的使用模式,而不能歸於模式本身。策略模式最大的缺點,可能就是我們需要維護一大堆類,但這個問題可以利用Factory 方法模式解決。         最後就是貼出我們上面例子的UML圖,策略模式的UML圖大概就是這樣:         
      

 

 

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.