前段時間買了一本書《Head First設計模式》,看了第一章后才對設計模式的概念有少許了解:它其實是開發過程中很多前人的經驗與智慧的總結,幫助你在開發時采取更好的方式去設計各個類、方法、以及它們之間的調用、實現方式,讓代碼保持靈活性的同時又能更好地復用。基於學過一塊知識一定要用文字記錄、總結、鞏固,而不是走馬觀花的原則,趁最近終於有空,特將前一段時間看的關於“策略模式”的內容總結於此。
場景描述
A公司要做一套模擬鴨子的游戲,游戲中會出現各種鴨子,一邊游泳戲水,一邊呱呱叫,還有一些會飛。
方案1 繼承
設計一個超類Duck,包含方法quack()、swim()、fly()分別模擬鴨子的叫、游泳、飛行等行為,再包含一個抽象類display(),用於展示各個鴨子不同的外觀,讓每個鴨子子類繼承父類時實現display()。
弊端:
這樣做的好處是,每個鴨子子類繼承父類時就同時擁有了父類的方法,可以達到代碼復用的目的,但是這樣會使某些並不適合該行為的子類也具有該行為。如果某些子類鴨子,如“橡皮鴨”,它不具備某些功能(如飛行),它就不應該擁有這個飛行的功能。當然,你可以在子類中通過@Override覆蓋這個方法。但是當各個不同的子類都需要通過覆蓋修改不同的方法時,就會非常繁瑣,而且容易出現紕漏,且這些新覆蓋的方法不能被復用。如“橡皮鴨”只會叫不會飛,“木頭鴨”不會叫也不會飛。以后每當有新的鴨子子類出現,你都要去檢查並可能需要覆蓋這些方法,想想都讓人抓狂。
方案2 接口
在超類Duck中將quack()、fly()等可變的方法用接口Quackable(),Flyable()來代替,然后在每個鴨子子類中,如果具有“飛行”或“叫”這個功能就實現“飛行“或”叫“這個接口。
弊端:
代碼無法復用,如果有100個子類,都具有飛行的行為,你就需要重復100次代碼。
設計模式來幫忙
設計原則一:找出程序中可能需要變化的地方和不需要變化的地方,將它們獨立開來。讓系統中的某部分改變不會影響其他部分。
由於fly()和quack()會隨着鴨子的不同而改變,所以把這兩個行為從Duck類中分開,建一組新類來代表各個行為。
設計原則二:針對接口編程,而不是針對實現。
利用多態,針對超類型編程,執行時根據實際狀況執行到真正的行為,不會被綁死在超類型的行為上。以前的做法是:行為來自超類的具體實現或是繼承某個接口並由子類自行實現。這兩種方法都捆綁於”實現“,無法方便地更改行為。現在我們利用接口代表每個行為,比如FlyBehavior,QuackBehavior,然后讓各個行為類實現這些接口,然后在Duck類中只要定義這個接口的實例變量即可,這樣在各個鴨子子類中如果想擁有某種特定的行為,只要用這個接口實例變量去引用具體的行為類即可。
設計原則三:多用組合,少用繼承。
飛行和叫這兩種不同的行為,我們分別為其建立兩組不同的行為類,然后在Duck類中通過接口實例變量結合起來,這就是”組合“。使得系統具有很大的彈性,還可以”在運行時動態地改變行為“。
下面就是經過重新設計后的應用代碼:
1.應用的目錄結構圖

2.接口FlyBehavior
1 public interface FlyBehavior { 2 public void fly(); 3 }
3.接口QuackBehavior
1 public interface QuackBehavior { 2 public void quack(); 3 }
4.Fly行為的一個實現——FlyNoWay
1 public class FlyNoWay implements FlyBehavior{ 2 @Override 3 public void fly() { 4 System.out.println("I can not fly."); 5 } 6 }
5.Fly行為的另一個實現——FlyWithWings
1 public class FlyWithWings implements FlyBehavior { 2 @Override 3 public void fly() { 4 System.out.println("I can fly!"); 5 } 6 }
6.Fly行為的又另一個實現——FlyRocketPowered
1 public class FlyRocketPowered implements FlyBehavior{ 2 @Override 3 public void fly() { 4 System.out.println("I am flying with a rocket."); 5 } 6 }
7.父類Duck
1 public abstract class Duck { 2 FlyBehavior flyBehavior; 3 QuackBehavior quackBehavior; 4 public abstract void display(); 5 public void performFly(){ 6 flyBehavior.fly(); 7 } 8 public void performQuack(){ 9 quackBehavior.quack(); 10 } 11 public void setFlyBehavior(FlyBehavior fb){ 12 this.flyBehavior = fb; 13 } 14 public void setQuackBehavior(QuackBehavior qb){ 15 this.quackBehavior=qb; 16 } 17 }
8.Duck的一個子類——綠頭鴨MallardDuck
1 public class MallardDuck extends Duck{ 2 public MallardDuck() { 3 flyBehavior = new FlyWithWings(); 4 quackBehavior = new QuackWithGuaGua(); 5 } 6 7 @Override 8 public void display() { 9 System.out.println("I am a MallardDuck."); 10 11 } 12 }
9.Duck的另一個子類——模型鴨ModelDuck
1 public class ModelDuck extends Duck { 2 public ModelDuck() { 3 flyBehavior = new FlyNoWay(); 4 quackBehavior = new QuackNoWay(); 5 } 6 7 @Override 8 public void display() { 9 System.out.println("I am a ModelDuck."); 10 } 11 }
10.應用模擬器(執行主類):MiniDuckSimulator
1 public class MiniDuckSimulator { 2 3 public static void main(String[] args) { 4 Duck mallardDuck = new MallardDuck(); 5 mallardDuck.display(); 6 mallardDuck.performFly(); 7 mallardDuck.performQuack(); 8 Duck modelDuck = new ModelDuck(); 9 modelDuck.display(); 10 modelDuck.performFly(); 11 modelDuck.performQuack(); 12 modelDuck.setFlyBehavior(new FlyRocketPowered()); 13 modelDuck.performFly(); 14 } 15 16 }
執行代碼,得到的結果如下:

總結
策略模式定義了算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化獨立於使用算法的客戶。這是書里給出的策略模式的定義,依我個人的理解,這種模式的關鍵就是要將系統中的可變行為抽象出來,單獨進行封裝,用一組行為類(算法族)來實現該特定的接口,這樣任何類(如Duck)如果想擁有這些算法族中的某個算法,都可以通過定義接口實例變量而擁有該整個算法族,在子類中再對該變量進行賦值。像這個例子里,通過定義了FlyBehavior,這樣以后任何想有飛行行為的類如飛機,都可以調用這個接口及實現它的各個飛行類。且飛行行為的變化可以通過增加新的飛行類來實現,不會對其他部分(如quack()、display()等行為,亦或是已擁有某些特定飛行行為的對象)造成任何影響,即”算法的變化獨立於使用算法的客戶“。而且各個飛行行為之間也可以互相替換。即 setFlyBehavior(Flybehavior fb)。
可見,設計模式的應用讓整個項目的代碼擁有了極大的靈活性,且達到了代碼復用的效果。設計模式其實是一種設計上的思維方式,是前人的智慧和經驗的總結,其真正的精髓不是看過就能學會的,還是需要在實際應用中不斷地實踐摸索,慢慢體會。
文章中如果有任何問題,歡迎大家指正。
