策略模式算法
定義了算法族,分別封裝起來,讓他們之間能夠互相替換,此模式讓算法的變化獨立於使用算法的客戶。編程
舉一個簡單的例子來描述策略模式。ide
設計一款冷兵器時代士兵打仗的遊戲,遊戲內部設計要使用OO技術。函數
首先,設計一個士兵對象(Soilder)做爲父類,而後在設計許多不一樣種類的士兵對象來繼承士兵這個父類,好比:長槍兵(Spearman)、騎兵(Cavalryman)、弓箭手(Bowman)等等,設計好後進行討論,以爲不錯沒有問題,能夠開始開發,測試,遊戲公測……一切都不錯,具體以下面的類圖所示:測試
在遊戲運營了一段時候以後,發現須要一些新元素來吸引玩家,要求不一樣種類的士兵的攻擊動做不一樣,而且要求士兵須要有游泳、攀爬等技能。使用OO技術很是好解決啊,打開類的設計圖開始幹活吧!能夠把hit方法設計成抽象方法,讓每種士兵都實現本身的hit,而後在Soilder類中加入游泳,攀爬的方法,修改的類圖以下:this
使用這種設計方式後,在遊戲運行的過程當中咱們會看到,騎兵騎着馬在爬樹。顯然的,在設計的時候忽略了一點,並不是全部的士兵均可以游泳和攀爬。有許多不可作這些行爲的士兵,對代碼的局部修改,影響的層面可不只是局部。spa
使用繼承如何?.net
把swim和clmb放到子類中,覆蓋掉父類的對應方法,像hit的作法同樣。可若是之後要加入其餘類型的士兵又會如何?好比重甲步兵沒辦法游泳也沒辦法攀爬,由於重甲過重了,而騎兵則沒法騎着馬攀爬,但能夠騎着馬過河等等。可見利用繼承來提供士兵的行爲會形成:設計
1.代碼在多個子類中重複;3d
2.運行時的行爲不容易改變;
3.很難知道全部士兵的所有行爲;
4.改變會牽一髮而動全身,形成其餘某些類型的士兵不想要的行爲;
利用接口如何?
能夠加入ISwin和IClimb接口,把swim函數和climb函數從父類中取出來,並被子類實現,以下圖所示:
但士兵的規格會經常改變,每當有新的士兵類型出現,就要被迫檢查兵可能須要實現ISwim和IClimb接口。而且這麼以來重複的代碼會變多,由於實現的能夠游泳和爬樹的代碼是同樣的。好比有一天swim的方式發生了變化,現有的50個士兵子類都須要修改swim函數,這是不能夠接受的。
如何解決?
能夠看出,並不是全部的子類都會游泳和爬樹,因此繼承並非適當的解決方式。雖然使用接口能夠解決一部分問題,可是卻形成了代碼沒法複用,幸運的是,有一個原則剛好用於此處。
設計原則1
找出應用中可能須要變化之處,把它們獨立出來,不要和那些不須要變化的代碼混在一塊兒。
也就是說,每次需求一來,都會使某些方面的代碼發生變化,那麼基本能夠肯定這部分代碼須要被抽出來和其餘穩定的代碼有所區分。
如今,爲了要分開變化和不會變化的部分,咱們準備創建兩組類,一個是swim相關的,一個是clmb相關的,而且要作到一切能有彈性,正由於一開始設計的行爲沒有彈性才致使種種問題。比方說,我要產生一個新的類型的輕甲士兵,並指定行爲攀爬給他,也就是說在士兵類中包涵設置行爲的方法,這樣就能夠在運行時動態改變行爲,有了這些目標要實現,就引出了第二個設計原則。
設計原則2
針對接口或抽象類編程,而不是針對實現編程。
咱們利用接口表明每一個行爲(Behavior),比方說SwimBehavior和ClimbBehavior,而行爲的每一個實現都將實現其中的一個接口。Soilder類不會負責實現它們,反而是由一組其餘類專門去實現,並整合到Soilder類中,具體的作法是在Soilder類中加入兩個實例變量,分別爲SwimBehavior和ClimbBehavior,聲明爲接口類型,每一個士兵子類都會動態的設置這些變量在運行時引用正確的行爲類型。看一下類圖與實現的代碼(Java代碼)。
package cn.net.bysoft.Strategy; // 士兵 public abstract class Soldier { // 士兵的名字 private String name; private SwimBehavior swimBehavior; private ClimbBehavior climbBehavior; public Soldier() { super(); } public Soldier(String name) { super(); this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public SwimBehavior getSwimBehavior() { return swimBehavior; } public void setSwimBehavior(SwimBehavior swimBehavior) { this.swimBehavior = swimBehavior; } public ClimbBehavior getClimbBehavior() { return climbBehavior; } public void setClimbBehavior(ClimbBehavior climbBehavior) { this.climbBehavior = climbBehavior; } // 顯示士兵的信息 public void display() { System.out.println("這個士兵的名字:" + name); } // 士兵攻擊敵人。 public abstract void hit(); // 委託給行爲去處理。 public void swim() { swimBehavior.swim(); } public void climb() { climbBehavior.climb(); } }
package cn.net.bysoft.Strategy; // 長槍兵 public class Spearman extends Soldier { public Spearman() {} public Spearman(String name) { super(name); } @Override public void hit() { // TODO Auto-generated method stub System.out.println("士兵使用長槍去攻擊敵人"); } } package cn.net.bysoft.Strategy; // 騎兵 public class Cavalryman extends Soldier { public Cavalryman() {} public Cavalryman(String name) { super(name); } @Override public void hit() { // TODO Auto-generated method stub System.out.println("騎士在立刻用刀攻擊敵人"); } } package cn.net.bysoft.Strategy; // 弓箭手 public class Bowman extends Soldier { public Bowman() {} public Bowman(String name) { super(name); } @Override public void hit() { // TODO Auto-generated method stub System.out.println("弓箭手拉弓瞄準射擊敵人"); } }
package cn.net.bysoft.Strategy; // 游泳行爲的接口。 public interface SwimBehavior { // 只需在此定義一個游泳的行爲方法便可。 public void swim(); } package cn.net.bysoft.Strategy; // 不能游泳 public class SwimNoWay implements SwimBehavior { public void swim() { // TODO Auto-generated method stub System.out.println("這個士兵不能游泳"); } } package cn.net.bysoft.Strategy; // 士兵本身游泳,不經過其餘途徑。 public class SwimWithBody implements SwimBehavior{ public void swim() { // TODO Auto-generated method stub System.out.println("士兵本身游泳過河"); } } package cn.net.bysoft.Strategy; // 士兵騎馬游泳 public class SwimWithHouse implements SwimBehavior { public void swim() { // TODO Auto-generated method stub System.out.println("士兵騎馬過河"); } }
package cn.net.bysoft.Strategy; // 攀爬行爲的接口 public interface ClimbBehavior { public void climb(); } package cn.net.bysoft.Strategy; public class ClimbNoWay implements ClimbBehavior { public void climb() { // TODO Auto-generated method stub System.out.println("這個士兵不能攀爬"); } } package cn.net.bysoft.Strategy; // 士兵爬樹。 public class ClimbTree implements ClimbBehavior { public void climb() { // TODO Auto-generated method stub System.out.println("士兵開始爬樹"); } }
package cn.net.bysoft.Strategy; public class Game { public static void main(String[] args) { // TODO Auto-generated method stub // 一個玩家創建一個長矛兵進入遊戲 Soldier spearman = new Spearman("長矛高手"); spearman.hit(); // 設置這個士兵的行爲,能夠本身游泳,爬樹。 spearman.setSwimBehavior(new SwimWithBody()); spearman.setClimbBehavior(new ClimbTree()); spearman.swim(); spearman.climb(); System.out.println(" ============================= "); // 另外一個玩家創建士兵進入遊戲 Soldier cavalryman = new Cavalryman("圓桌騎士"); cavalryman.hit(); // 設置這個士兵的行爲,能夠騎馬過河,可是不能夠爬樹。 cavalryman.setSwimBehavior(new SwimWithHouse()); cavalryman.setClimbBehavior(new ClimbNoWay()); cavalryman.swim(); cavalryman.climb(); } }
總結一下,請特別注意類之間的關係,關係能夠是IS-A(是一個)也能夠是HAS-A(有一個)或IMPLEMENTS(實現)。HAS-A關係至關有趣,每個士兵都有SwimBehavior和ClimbBehavior的行爲,並將這些動做交給它們去處理,當將兩個類結合起來使用,就是這種組合。這種作法和繼承不一樣的地方在於,行爲不是繼承來的,而是和適當的行爲對象組合來的。這是一個很重要的技巧,騎士是使用了第三個設計原則:
設計原則3
多用組合,少用繼承。
使用組合創建系統具備很大的彈性,不只能夠將算法族封裝成類,更能夠在運行時動態地改變行爲,只要組合的行爲對象符合正確的接口標準便可。以上就是策略模式的介紹。