一.什么是策略模式(Strategy Pattern)?
從字面上理解,策略模式就是應用了某種“策略”的設計模式,而這個“策略”就是:把變化的部分封裝起來。
其實這個理解有誤,也是本文被反對一次的原因,例子沒錯,但對此模式的理解有偏差,修改內容已經追加在本文尾部,點我跳轉>>
二.舉個例子
假定現在我們需要用類來描述Dog
首先,所有的Dog都有外形(比如Color),有行為(比如Run)
於是我們很自然地定義了這樣一個基類Dog:
public abstract class Dog { public abstract void display();//顯示Dog的外形 public abstract void run();//定義Dog的Run行為 }
其它的Dog具體類全部繼承自Dog基類就好了,很快,需求來了
有一只Red Dog,它是寵物犬,跑得很慢,叫聲也很小
於是我們又定義了RedDog類:
public class RedDog { @override public void display(){ System.out.println("this is a red dog."); } @override public void run(){ System.out.println("it's running slowly."); } }
接着,來了一大批需求,Black Dog、Gray Dog、Pink Dog、BlackGrayDog、PinkGrayDog。。。一共100只Dog,除Color不同外,Run的速度也不同
於是我們定義了100個類來表示100種不同類型的Dog,每個類中都實現了Run方法與Display方法。
嗯~,看看我們的類圖,沒錯,這個BigBang就是我們的類圖了,好像很合理啊,有100種Dog當然會有100個類啊,而且具體Dog類當然要繼承自Dog基類吧?嗯,好像是這樣的,而且這樣好像也沒什么不好,至少現在看不出來有什么不妥。。。
-------
又一個新的需求來了,我們發現Dog不僅可以Run,還可以Bark
自然而然地,我們想到了給Dog基類定義一個新行為Bark,然后在100個具體類中逐一重寫了Bark方法。。。
雖然過程有些麻煩,不過好在我們還是解決了問題,現在所有的Dog都可以Bark了
-------
這天,來了一只新Dog,它叫ToyDog,是玩具狗,不會Run也不能Bark,只是個玩偶
於是我們讓ToyDog繼承了Dog基類,重寫了Run方法和Bark方法(方法體為空,因為Toy不會Run也不會Bark)
問題好像也被完美解決了,至少現在看來不存在什么問題
-------
很多天后,ToyDog工廠技術革新了,新產品被裝上了電池,可以Bark了
沒關系,我們修改了ToyDog類,實現了Bark
-------
我們初始化了一只ToyDog,它歡快的BarkBarkBark,過了一會兒,電池沒電了,ToyDog不能Bark了,但是我們的ToyDog類顯示它還可以Bark。。。
*******
現在,終於出問題了吧,我們發現Dog問題中最難處理的部分就是Run、Bark這兩個行為,一旦發生變化我們就需要修改具體Dog類,甚至是Dog基類,不僅需要花費大量的時間,而且所有具體Dog類中都實現了Run與Bark方法,顯得很臃腫。還有最重要的問題,我們無法動態地修改Dog的行為,比如小Dog跑得慢叫聲小,長大后跑得快叫聲大,也無法應對上面提到的電池沒電導致的行為變化問題。。。。這一系列的問題想向我們說明一點:這從一開始就是一個糟糕的設計。
三.如何應用策略模式?
策略模式要求把變化的部分封裝起來,首先,我們要找到代碼中頻頻發生變化的部分
在上一個例子中,變化的部分是什么?
1.Run行為
2.Bark行為
3.其它可能存在的行為
...
下面我們把這些行為封裝起來(以Run為例):
package StrategyPattern; /** * @author ayqy * 定義Run接口 * */ public interface IRun { public void run();//定義run行為 }
package StrategyPattern; /** * @author ayqy * 實現RunQuickly行為 * */ public class RunQuickly implements IRun{ @Override public void run() { System.out.println("it's running quicky."); } }
package StrategyPattern; /** * @author ayqy * 實現RunSlowly行為 * */ public class RunSlowly implements IRun{ @Override public void run() { System.out.println("it's running slowly."); } }
package StrategyPattern; /** * @author ayqy * 實現RunNoWay行為 * */ public class RunNoWay implements IRun{ @Override public void run() { System.out.println("it isn't able to run."); } }
Bark行為的封裝與之類似
封裝好“變化”之后,我們的Dog基類也要做相應改變:
package StrategyPattern; /** * @author ayqy * 定義Dog基類 * */ public abstract class Dog { IBark bark; IRun run; public abstract void display();//顯示Dog的外形 public IBark getBark() { return bark; } public void setBark(IBark bark) { this.bark = bark; } public IRun getRun() { return run; } public void setRun(IRun run) { this.run = run; } }
注意,我們只封裝了容易發生變化的部分(Bark與Run),而沒有封裝Display方法(Dog的外形應該比較fixed吧),什么才是“變化的部分”?這需要我們仔細考慮,視具體情景而定
現在來看看我們新的具體Dog類吧
package StrategyPattern; /** * @author ayqy * 實現RedDog * */ public class RedDog extends Dog{ public RedDog() { //紅狗是寵物狗 //跑得慢,叫聲小 super.setRun(new RunSlowly()); super.setBark(new BarkQuietly()); } @Override public void display() { System.out.println("this is a red dog."); } }
類被明顯瘦身了吧,而且我們現在還能在運行時動態地修改Dog的行為,看到策略模式的威力了吧。
四.效果示例
編寫一個測試類:
package StrategyPattern; /** * @author ayqy * 測試策略模式<br/> * 策略模式,簡單的說就是要求把“變化”封裝起來,以隔離“變化”對其它部分的影響 * */ public class Test { /** * @param args */ public static void main(String[] args) { System.out.println("------- 創造一只Red Dog -------"); Dog redDog = new RedDog(); showDogInfo(redDog); System.out.println("\n------- 創造一只Black Dog -------"); Dog blackDog = new BlackDog(); showDogInfo(blackDog); System.out.println("\n------- 創造一只Toy Dog -------"); Dog toyDog = new ToyDog(); showDogInfo(toyDog); System.out.println("\n------- 技術革新,現在Toy Dog可以小聲叫了 -------"); toyDog.setBark(new BarkQuietly()); showDogInfo(toyDog); //上面的代碼並不是最優的,只是為了說明策略模式 //一個很明顯的問題是Dog redDog = new RedDog(); //我們在面向具體對象編碼,而不是設計模式所提倡的面向接口編碼 //可以定義一個Dog工廠來生產Dog對象以求模塊之間更松的耦合 } /** * @param dog * 顯示Dog相關信息 */ private static void showDogInfo(Dog dog) { dog.display(); dog.run.run(); dog.bark.bark(); } }
運行結果示例:
五.總結
策略模式的核心是要把頻繁發生變化的部分封裝起來,作用是把變化部分的影響隔離開,避免局部的變化對其它fixed部分造成影響,設計時可能需要更多的時間,但便於維護、復用與擴展,在本例中,Run、Bark行為都可以在新的類(如Pig)中復用;一旦行為發生變化我們只需要修改各個行為接口,最多再對Dog基類做簡單修改就可以從容地應對變化了。
上面的內容有些偏差,但勉強可以看,只是不太准確,下面作以准確的描述:
策略模式的思想確實是封裝,但這里的”策略“並不是指”把變化封裝起來“,這是在反復讀了幾遍原文后發現的(《Head First 設計模式》第一章感覺有點不太好,第一遍理解偏了)
這里的”策略“近似於算法
策略模式不太容易理解(雖然筆者極力避免照搬原文,但萬一再理解偏了就誤導別人了。。),書上的准確定義是這樣的:
策略模式定義了算法族,分別封裝起來,讓它們之間可以互相替換,此模式讓算法的變化獨立於使用算法的客戶。
個人反復讀過幾遍之后,覺得原文想表達的東西是這樣的:
- 1.策略模式核心是對算法的封裝(還有一種設計模式叫”模版方法模式“,也是對算法的封裝,區別與聯系放在里面介紹,點我跳轉>>)
- 2.專注於實現算法(策略)的選擇,支持運行時動態改變策略
- 3.具體實現是把變化的部分找出來,定義為接口,每個接口對應一組算法,每一個都是一種策略
- 4.同一接口下的算法是可以相互替換的
- 5.算法是獨立於客戶代碼的,也就是對算法封裝的具體體現