《Head First設計模式》看了一部分才對設計模式有了初步的了解:它其實是開發過程中很多前人的經驗與智慧的總結,幫助你在開發時采取更好的方式去設計各個類、方法、以及它們之間的調用、實現方式,讓代碼保持靈活性的同時又能更好地復用。基於學過一塊知識一定要用文字記錄、總結、鞏固,而不是走馬觀花的原則,趁最近終於有空,特將前一段時間看的關於“策略模式”的內容總結於此。
一、場景描述
A公司要做一套模擬鴨子的游戲,游戲中會出現各種鴨子,一邊游泳戲水,一邊呱呱叫,還有一些會飛。
方案1 繼承
設計一個超類Duck,包含方法quack()、swim()、fly() 分別模擬鴨子的叫、游泳、飛行等行為,再包含一個抽象類 display() ,用於展示各個鴨子不同的外觀,讓每個鴨子子類繼承父類時實現display();
弊端:
這樣做雖然每個鴨子子類繼承父類時就同時擁有了父類的方法,可以達到代碼復用的目的,但是這樣會使某些並不適合該行為的子類也具有該行為。如果某些子類鴨子,如“橡皮鴨”,它不具備某些功能(如飛行),它就不應該擁有這個飛行的功能。當然,你可以在子類中通過 @Override 覆蓋這個方法。但是當各個不同的子類都需要通過覆蓋修改不同的方法時,就會非常繁瑣,而且容易出現紕漏,且這些新覆蓋的方法不能被復用。如“橡皮鴨”只會叫不會飛,“木頭鴨”不會叫也不會飛。以后每當有新的鴨子子類出現,你都要去檢查並可能需要覆蓋這些方法,想想都讓人抓狂。
方案2 接口
在超類 Duck 中將quack()、fly() 等可變的方法用接口 QuackBehavior 接口中 Quackable(),FlyBehavior 接口中 Flyable() 來代替,然后在每個鴨子子類中,如果具有“飛行”或“叫”這個功能就實現“飛行“或”叫“這個接口。
弊端:
代碼無法復用,如果有100個子類,都具有飛行的行為,你就需要重復100次代碼。
二、設計模式來幫忙
設計原則一:找出程序中可能需要變化的地方和不需要變化的地方,將它們獨立開來。讓系統中的某部分改變不會影響其他部分;
由於 fly() 和 quack() 會隨着鴨子的不同而改變,所以把這兩個行為從 Duck 類中分開,建一組新類來代表各個行為。
設計原則二:針對接口編程,而不是針對實現;
利用多態,針對超類型編程,執行時根據實際對象執行到真正的行為,不會被綁死在超類型的行為上。以前的做法是:行為來自超類的具體實現或是繼承某個接口並由子類自行實現。這兩種方法都捆綁於”實現“,無法方便地更改行為。現在我們利用接口代表每個行為,比如FlyBehavior,QuackBehavior,然后讓各個行為類實現這些接口,然后在 Duck 類中只要定義這個接口的實例變量即可,這樣在各個鴨子子類中如果想擁有某種特定的行為,只要用這個接口實例變量去引用具體的行為類即可。
設計原則三:多用組合,少用繼承;
飛行和叫這兩種不同的行為,我們分別為其建立兩組不同的行為類,然后在 Duck 類中通過接口實例變量結合起來,這就是”組合“。使得系統具有很大的彈性,還可以”在運行時動態地改變行為“。
三、代碼如下
(1)目錄結構
(2)接口 FlyBehavior 和 QuackBehavior
public interface FlyBehavior { void fly(); } public interface QuackBehavior { void quack(); }
(3)FlyBehavior 實現類【QuackBehavior 實現類和 FlyBehavior 類似】
public class FlyNotWay implements FlyBehavior { @Override public void fly() { System.out.println("I can not fly"); } } public class FlyWithSwings implements FlyBehavior { @Override public void fly() { System.out.println("I can fly"); } } public class FlyRocketPowered implements FlyBehavior { @Override public void fly() { System.out.println("I can fly with rocket"); } }
(4)鴨子外觀實現類 MallardDuck 野鴨子和 ModelDuck 模型鴨子
public class MallardDuck extends Duck { public MallardDuck() { flyBehavior = new FlyWithSwings(); quackBehavior = new QuackWithGaGa(); } @Override public void display() { System.out.println("I am a MallardDuck"); } } public class ModelDuck extends Duck { public ModelDuck() { flyBehavior = new FlyNotWay(); quackBehavior = new QuackNotWay(); } @Override public void display() { System.out.println("I am a ModelDuck"); } }
(5)Duck 抽象類
public abstract class Duck { protected FlyBehavior flyBehavior; protected QuackBehavior quackBehavior; /** * 鴨子外觀方法 */ public abstract void display(); public void performFly() { flyBehavior.fly(); } public void performQuack() { quackBehavior.quack(); } public void setFlyBehavior(FlyBehavior fb) { this.flyBehavior = fb; } public void setQuackBehavior(QuackBehavior qb) { this.quackBehavior = qb; } }
(6)執行結果
I am a MallardDuck
I can fly
I can quack
I am a ModelDuck
I can not fly
I can not quack
I can fly with rocket
四、總結
策略模式定義了算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化獨立於使用算法的客戶。這是書里給出的策略模式的定義,依我個人的理解,這種模式的關鍵就是要將系統中的可變行為抽象出來,單獨進行封裝,用一組行為類(算法族)來實現該特定的接口,這樣任何類(如 Duck)如果想擁有這些算法族中的某個算法,都可以通過定義接口實例變量而擁有該整個算法族,在子類中再對該變量進行賦值。
像這個例子里,通過定義了FlyBehavior,這樣以后任何想有飛行行為的類如飛機,都可以調用這個接口及實現它的各個飛行類。且飛行行為的變化可以通過增加新的飛行類來實現,不會對其他部分(如quack()、display() 等行為,亦或是已擁有某些特定飛行行為的對象)造成任何影響,即”算法的變化獨立於使用算法的客戶“。而且各個飛行行為之間也可以互相替換;即 setFlyBehavior(FlyBehavior fb)。
可見,設計模式的應用讓整個項目的代碼擁有了極大的靈活性,且達到了代碼復用的效果。設計模式其實是一種設計上的思維方式,是前人的智慧和經驗的總結,其真正的精髓不是看過就能學會的,還是需要在實際應用中不斷地實踐摸索,慢慢體會。
策略模式和橋接模式的區別
(1)策略模式和橋接模式經過學習發現,都是將一個事物的抽象和它的實現分離開來,使其兩者具有一定的獨立擴展的可能性,但是兩者有什么區別呢?
(2)橋接模式強調接口僅提供基本操作,而這些基本操作定義更高層次的操作,內部可以獨立擴展,可不屬於一個整體;策略模式強調抽象接口提供的是一種算法,Context簡單調用這些算法完成操作;
參考橋接模式:https://www.cnblogs.com/blogtech/p/12874663.html
策略模式深層了解:https://www.cnblogs.com/kubixuesheng/p/5155644.html
(3)橋接模式通過繼承、聚合的方式組合類和對象形成更大的結構;而策略模式則通過接口之間的協作完成不同功能的組合,是一種行為模式(重點理解)
(4)橋接模式要表達的內容比較多,結構也比較復雜,其實是接口隔離的原則,本質上把布局類的兩種體系區別開來,使得他們可以松散的組合,而策略模式在解耦只是某一個算法級別的層次。客觀上講:策略模式只是橋接模式的 一部分;