你看那裏有隻小鴨喲,你看那裏又只小鴨喲~小鴨嘎嘎嘎-ga-喲~小鴨嘎嘎嘎-ga-喲~
矮油我艹,居然被這神曲給洗腦了。現在一看到小動物就莫名其妙的會哼唱起來
,那麼問題來了,長頸鹿怎麼叫?“好長~好長~”
是這樣叫的麼?這個問題真的很逗逼耶……
好啦,不扯淡了,原歸正傳。我要講的主題就是小鴨子!哦sorry,準確的應該說是策略模式。因爲策略模式中最經典例子莫過於鴨子模型的設計啦。之前有很多大神都寫過文章講解,講得都非常深入和細緻。但我這篇文章中,只是大略的以個人理解思維去闡述一個故事,如有講得不足之處還望見諒。
故事的開頭是這樣,會議上老闆說:“最近接了個玩具廠的項目,要我們做個鴨子程序。沒啥需求,就是要有大黃鴨、青頭鴨、醜小鴨、唐老鴨……它們都會叫會飛!好啦,這種小項目就交給剛來實習的小林去做吧。”我胸有成足的接下了這個任務,心想這小兒科so easy ~
先做個鴨子接口:
public interface Ducks { /** * 外貌 */ abstract void display(); /** * 叫聲 */ abstract void quack(); /** * 飛行 */ abstract void fly(); /** * 游泳 */ public void swim(); }
在寫鴨子接口的實現類:
public class Green implements Ducks { @Override public void display() { System.out.println("我是青頭鴨~~"); } @Override public void quack() { System.out.println("gaga~叫聲很好聽"); } @Override public void fly() { System.out.println("飛得老高了我~~"); } @Override public void swim() { System.out.println("遊的老快了~"); } }
…………
最後呢,在 if-else if- 來判斷我要調用的是那個模型的鴨子:
public void selectDuck(String type) { if (type.equals("yellow")) { Ducks duck = new Green(); duck.quack(); } else if (type.equals("green")) { Ducks duck = new Yellow(); duck.fly(); } }
恩,很快的項目完成了。我高高興興的把第一版本給提交了。老闆哪會管你是如何實現的,只要你能完成預期的功能效果就ok。正當我還在得意洋洋之時,問題就來了。玩具廠的說要讓有的鴨子不會飛,有的鴨子不會叫,有的鴨子不僅會叫還要回唱歌,唱歌還不夠,要給鴨子分等級,等級高的還會講故事……
呵呵,我勒個擦!那我豈不是要在每個鴨子接口和實現類中添加好多方法?還得在調用selectDuck中多做好多else if判斷?今天改了這些歌需求,萬一明天又突然給我加過別的需求,我不是又要重新改一遍代碼了麼?憂傷的我,一籌莫展,對着電腦發呆。我該怎麼辦纔好呢,讓我別每次都改動原來寫好的代碼,有新的需求的時候只要多添加個類就好了?
“今天怎~~~麼不開心~?”老周問我:“不用擔心,時間會給你答案~我的滑板鞋時尚時尚最時尚~”我吧事情跟老周詳細講了一遍後,老周笑道:“騷年,你還太年輕!這完全可以用策略模式去解決啊!”
於是乎老周就開始很裝逼的給我云云:
一、所謂的策略模式呢就是:
定義一系列的算法,把它們一個個封裝起來,並且使它們可相互替換。“親,您能說的直白點嗎?”“哦,抱歉。我向來都是這麼專業的。說白了,就是你不是有很多鴨子嗎?每個鴨子要有不同的功能,比如說飛行的算法,叫聲的算法,游泳的算法,你都統統把它們一個一個的封裝起來。到時候具體需要什麼樣的鴨子,就給客戶自己去選擇咯。”
二、那到底要怎麼去實現呢?
1. 對策略對象定義一個公共接口(FlyBehavior、QuackBehavior……接口;把所有的行爲都做成接口方便以後對策略的擴展)
2. 編寫具體策略類,該類實現了上面的接口(FlyNoWay、FlyWithWings、Quack、MuteQuack……以後要是有什麼新功能,就只需要去實現行爲接口,添加新的策略類咯)
3. 在使用策略對象的類(即:環境角色)中保存一個對策略對象的引用(Duck;最爲關鍵的就是這個引用對象了,既然我們是要創建鴨子,當然就要用鴨子去引用策略啦。)
4. 在使用策略對象的類中,實現對策略對象的set和get方法(注入)或者使用構造方法完成賦值(引用的小技巧就是用傳說中的"組合")
5. 客戶端進行調用 (調用不同的鴨子時就分配給它相應的策略咯)
扯了半天還是雲裏霧裏的感覺。老周啊,你妹圖扯個蛋呀!結果老周不服氣,馬上畫了張圖:
抽象策略角色:策略類,通常由一個接口或者抽象類實現。
具體策略角色:包裝了相關的算法和行爲。
環境角色:持有一個策略類的引用,最終給客戶端用的。
三、終於開竅了,老周啊,你真是中國好同事喲。按照老周說的硬是把代碼給敲出來了:
首先,鴨子喊叫和飛行的公共接口。
package com.strategy.quack; /** * 鴨子叫的接口 * @author linhy * */ public interface QuackBehavior { public void quack(); }
package com.strategy.fly; /** * 鴨子飛行接口 * @author linhy * */ public interface FlyBehavior { public void fly(); }
然後,就是這兩個接口的實現類。
package com.strategy.quack; /** * 會叫的鴨子類 * @author linhy * */ public class Quack implements QuackBehavior { @Override public void quack() { System.out.println("嘎嘎嘎~聽得到我在叫你麼……"); } }
package com.strategy.quack; /** * 不會叫的鴨子類 * @author linhy * */ public class MuteQuack implements QuackBehavior { @Override public void quack() { System.out.println("我是一隻安靜的沒鴨子,不會叫~"); } }
package com.strategy.fly; /** * 會飛的鴨子類 * @author linhy * */ public class FlyWithWings implements FlyBehavior { @Override public void fly() { System.out.println("我是在用翅膀飛翔,帶你裝B,帶你飛……"); } }
package com.strategy.fly; /** * 不會飛的鴨子類 * @author linhy * */ public class FlyNoWay implements FlyBehavior { @Override public void fly() { System.out.println("超人不會飛,鴨子也不會飛……"); } }
恩,策略都已經準備好了,接下來要開始引用它了。用什麼引用?當然是鴨子啦!但是,鴨子又有好多種類型的哦,所以要先把鴨子抽象下,方便以後再創建新類型鴨子……
package com.strategy.duck; import com.strategy.fly.FlyBehavior; import com.strategy.quack.QuackBehavior; /** * 鴨子抽象類 * @author linhy * */ public abstract class Duck { //------------組合方式引用鴨子策略-------------------- //引入鴨子的飛行行爲對象 private FlyBehavior flyBehavior; //引入鴨子的叫喚行爲對象 private QuackBehavior quackBehavior; //通過子類來設置鴨子的飛行行爲 public void setFlyBehavior(FlyBehavior flyBehavior) { this.flyBehavior = flyBehavior; } //通過子類來設置鴨子的叫喚行爲 public void setQuackBehavior(QuackBehavior quackBehavior) { this.quackBehavior = quackBehavior; } public void performFly() { flyBehavior.fly(); } public void performQuack() { quackBehavior.quack(); } // 鴨子的外貌不一樣哦,所以我要抽象~ abstract void display(); //鴨子都會游泳哦,游泳的方式就暫且都一樣吧…… public void swim() { System.out.println("All ducks float, even decoys"); } }
抽象的鴨子已經設計好咯,就要寫具體繼承它的子類咯。
package com.strategy.duck; import com.strategy.fly.FlyBehavior; import com.strategy.quack.QuackBehavior; /** * 青頭鴨 * @author linhy * */ public class GreenDuck extends Duck { //通過構造方法來設置引用的鴨子對象的行爲 public GreenDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { super.setFlyBehavior(flyBehavior); super.setQuackBehavior(quackBehavior); } @Override public void display() { System.out.println("我是一隻美麗動人的青頭鴨~~"); } }
package com.strategy.duck; import com.strategy.fly.FlyBehavior; import com.strategy.quack.QuackBehavior; /** * 小黃鴨 * @author linhy * */ public class YellowDuck extends Duck { //通過構造方法來設置引用的鴨子對象的行爲 public YellowDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) { super.setFlyBehavior(flyBehavior); super.setQuackBehavior(quackBehavior); } @Override public void display() { System.out.println("我是一隻美麗動人的小黃鴨~~"); } }
這下鴨子模型準備好了,策略方法也寫好了。那麼就開始調用測試下看看唄。
package test; import com.strategy.duck.GreenDuck; import com.strategy.duck.YellowDuck; import com.strategy.fly.FlyNoWay; import com.strategy.fly.FlyWithWings; import com.strategy.quack.MuteQuack; import com.strategy.quack.Quack; /** * 給不同類型的鴨子設置不同行爲的策略 * @author linhy * */ public class TestDuck { public static void main(String[] args) { //設置小黃鴨會飛和叫 YellowDuck yellowDuck = new YellowDuck(new FlyWithWings(), new Quack()); //設置青頭鴨不會飛,也不會叫 GreenDuck greenDuck = new GreenDuck(new FlyNoWay(), new MuteQuack()); System.out.println("***************************************"); yellowDuck.display(); yellowDuck.performFly(); yellowDuck.performQuack(); yellowDuck.swim(); System.out.println("*****************************************"); greenDuck.display(); greenDuck.performFly(); greenDuck.performQuack(); greenDuck.swim(); } }
結果是這樣的:
嘿嘿,是這樣的哦。別看上去好像感覺要寫那麼多類啊,接口啊……感覺好麻煩。當你要去維護,去新增功能的時候,你就會體會到一個好的設計模式是多麼的方便。好比,下次老闆要是提出:小林,我要一隻超級鴨(多功能的鴨子),會講故事、會跳舞……我只需要新增個些策略和超級鴨子類,鴨子抽象類稍微修改下就可以。不要像以前那種蠢方法,耦合度太高,要修改太多地方,代碼看上去也不整潔。
老周最後來了一句:圖羊圖森破~。不裝逼你會死呀,心裏暗罵到。但還是忘分感謝……畢竟成長的道路上就是要在不斷的挫折中學習。
結語:
嘖嘖,故事終於講完了,我知道自己講的相當爛,有好多東西沒說清。特附上一篇策略模式的好文章:《研磨設計模式之策略模式》