设计模式(一)————策略模式(张三的故事??)
引言
當我們完成一個復雜的業(yè)務中常常會面臨一個問題:針對一個對象的某個行為,不同的情境下有不同的處理方式;
就比如今天我要去上班,那么我需要以哪種交通方式去上班呢?可以有下面幾種選擇:
- 步行
- 公交
- 地鐵
- 自行車
- 開車
當然還會有更多的選擇,這只是列舉了幾種;我上班時會在不同的情況下選擇不同的交通工具,這就是不同處理方式;
如果在代碼中體現(xiàn),我們可以選擇用 if…else 或者 switch 來對不同的情境進行判斷,從而選擇相應的交通工具;這樣想當然很簡單,但是寫出來的代碼會很復雜,而且后期進行維護是很難的;
那么就需要想一個辦法將這個對象和這個行為分開,這個行為就是一個算法,這樣對于修改維護只需要針對這個算法就可以了;
這里就需要用到設(shè)計模式中的策略模式;
策略模式定義:
策略模式(Strategy Pattern)中,定義算法族(策略組),分別封裝起來,讓他們之間可以互相替換,此模式讓算法的變化獨立于使用算法的客戶;
下面我將會用一個模擬鴨子的例子來介紹這個模式;
張三的問題
某公司有一個模擬鴨子的業(yè)務,這個業(yè)務中將會定義多種不同類型的鴨子,有的會飛,有的會叫,并且飛行方式和叫聲可能也會有不同;現(xiàn)在這個業(yè)務交給了張三去做,張三以熟練的OO技術(shù),設(shè)計了一個父類為Duck,然后讓各種不同的鴨子去繼承這個父類,便輕而易舉的完成了這個項目;
類圖:
可以看到,紅頭鴨和綠頭鴨都繼承了Dunk并重寫了display方法,是不是很簡單,我們看看代碼:
Duck類:
// 鴨子類(抽象類) public abstract class Duck {// 抽象方法:顯示什么鴨子public abstract void display();// 飛行的方法public void fly() {System.out.println("I'm flying!!!");}// 叫的方法public void quack() {System.out.println("嘎嘎嘎");} }MallardDuck類
// 綠頭鴨 public class MallardDuck extends Duck{@Overridepublic void display() {System.out.println("我是一只綠頭鴨!!");} }RedheadDuck類
// 紅頭鴨 public class RedheadDuck extends Duck{@Overridepublic void display() {System.out.println("我是一只紅頭鴨!!");} }輸出測試一下:
// 測試相應的功能 public class MiniDuckSimulator {public static void main(String[] args) {Duck mallardDuck = new MallardDuck(); // 綠頭鴨mallardDuck.display();mallardDuck.fly();mallardDuck.quack();System.out.println("-----------------");Duck redheadDuck = new RedheadDuck(); // 紅頭鴨redheadDuck.display();redheadDuck.fly();redheadDuck.quack();System.out.println("-----------------");} }結(jié)果如下:
我是一只綠頭鴨!! I'm flying!!! 嘎嘎嘎 ----------------- 我是一只紅頭鴨!! I'm flying!!! 嘎嘎嘎 -----------------就在張三沾沾自喜的時候,這時產(chǎn)品經(jīng)理提出了新的要求:要加一種新的鴨子:橡皮鴨;
張三一想:橡皮鴨不能飛,而且橡皮鴨的叫聲是“吱吱吱”,那么只要創(chuàng)建一個橡皮鴨的類,依然繼承Dunk,然后重寫Dunk中的fly和quack方法不久可以了嘛;
于是他又加了一個類:
RubberDuck類
// 橡皮鴨不會飛,叫聲是吱吱吱,所以需要重寫所有方法 public class RubberDuck extends Duck{@Overridepublic void display() {System.out.println("我是一只橡皮鴨!!");}@Overridepublic void fly() {System.out.println("I can't fly!!!");}@Overridepublic void quack() {System.out.println("吱吱吱");} }這時張三突然意識到,這里使用繼承好像并不是特別完美,雖然實現(xiàn)了功能,但是RubberDuck類把父類中的所有方法都重寫了一遍,這樣的代碼就出現(xiàn)了重復啊;
張三又想:
Duck類中的fly和quack好像并不適用于所有鴨子的情況,有的鴨子不會飛,有的不會叫,有的叫聲不一樣,有的飛行方式不一樣。。。。這樣看來,這個父類并不是完美的;如果產(chǎn)品經(jīng)理讓我給鴨子多加一個游泳的行為,那么一旦加到Duck類中后,所有種類的鴨子可能都會面臨修改的可能,這樣也太麻煩了吧!那該怎么辦呢?
這里總結(jié)一下張三通過繼承來提供Duck行為存在的問題:
- 代碼在多個子類中重復
- 運行時的行為不易改變
- 很難知道不同的鴨子具體的行為
- 改變會牽一發(fā)動全身,造成其他種類鴨子不需要的改變;
該怎么做?
這時張三突然想到:可以使用一個Flyable和一個Quackable接口,只讓會飛的鴨子實現(xiàn)Flyable,會叫的鴨子實現(xiàn)Quackable接口不就可以了嘛;
真的可以嗎?
這樣的話就會重復的代碼會更多,如果需要修改飛行的行為,那么就需要對所有實現(xiàn)飛行接口的代碼進行修改;一旦需要加入新的行為,如果用接口,那就需要對所有的鴨子進行一個判斷并實現(xiàn)該行為;
因為在這里張三只聲明了三種類型的鴨子,如果是五十種呢?一百種呢?難道都需要一一修改嗎?
其實出現(xiàn)這些問題的本質(zhì)就是因為鴨子Duck的行為會在子類里不斷地改變,并且如果讓所有的子類都有這些行為也是不現(xiàn)實的;
且使用接口不能實現(xiàn)代碼,就無法達到代碼的復用,一旦修改了某個行為,就需要找到所有實現(xiàn)該行為的類去修改,不僅工作量更大,而且可能會出現(xiàn)新的錯誤;
這時李四給張三提建議:
只需要找到項目中可能需要變換的地方,并把這個變化獨立出來,不和那些不會變化的代碼混在一起不就可以了嗎?
李四的意思其實就是:把Dunk中會變化的部分取出來,單獨封裝起來,這樣就可以輕易實現(xiàn)更該和擴充該部分,且不會影響不會變化的內(nèi)容;
那么下面張三需要做的就是:將鴨子變化的行為從Duck中取出封裝起來了;
問題解決
張三對Duck進行一個分析:既然要分離變化的行為,那么在這個類中也就只有fly和quack行為會改變了,所以只需要把這倆行為拿出來然后封裝起來就可以了;
這時又有了一個新的問題:
張三希望代碼更有彈性,因為開始的代碼沒有彈性才這樣做的,如果能夠動態(tài)的改變鴨子的行為,那樣一旦有需求改變肯定會容易很多;
張三靈機一動,想到了一個設(shè)計原則:
面向接口編程,而不是針對實現(xiàn)編程;
那么就是用接口來抽象這個行為,具體行為的表現(xiàn)模式實現(xiàn)這個接口就可以了;
所以張三準備了一個QuackBehavior接口和一個FlyBehavior接口,然后將他們聚合到Duck類中,這樣就可以靈活的修改代碼了;
由于產(chǎn)品經(jīng)理提出了新的需求:增加一個不會飛不會叫的模型鴨,并且給它加一個火箭助力器,讓它可以飛;
張三想:正好我在重新設(shè)計代碼,不如就拿這個來試試代碼,看看能不能達到預期要求;
張三先設(shè)計了QuackBehavior接口和FlyBehavior接口的類圖
那么Dunk該怎么設(shè)計呢?我們可以讓Dunk關(guān)聯(lián)于這兩個接口,這樣就可以讓Dunk類使用對應的方法了;
類圖:
張三這次留了個心眼,為了能夠?qū)崿F(xiàn)運行時代碼的動態(tài)拓展,所以加入了set方法,這樣就可以隨時隨地的設(shè)置不同鴨子的行為了;
接下來就是實現(xiàn)代碼了;
FlyBehavior接口
// 鴨子飛的接口 public interface FlyBehavior {public void fly(); }實現(xiàn)FlyBehavior接口:
// 用翅膀飛 public class FlyWithWings implements FlyBehavior{@Overridepublic void fly() {System.out.println("I'm flying!!!");} } // 不能飛 public class FlyNoWay implements FlyBehavior{@Overridepublic void fly() {System.out.println("I can't flying");} } // 火箭噴射器飛 public class FlyWithRocket implements FlyBehavior {@Overridepublic void fly() {System.out.println("Fly with a rocket!!");} }QuackBehavior接口
// 鴨子叫的接口 public interface QuackBehavior {public void quack(); }實現(xiàn)QuackBehavior接口
// 鴨子嘎嘎叫 public class Quack implements QuackBehavior{@Overridepublic void quack() {System.out.println("嘎嘎嘎");} } // 橡皮鴨吱吱叫 public class Squeak implements QuackBehavior{@Overridepublic void quack() {System.out.println("吱吱吱");} } // 不會叫 public class MuteQuack implements QuackBehavior{@Overridepublic void quack() {System.out.println("我不會叫");} }下面就是Duck類和具體不同種類的鴨子了
Dunk類
終于實現(xiàn)了所有的功能,張三懷著忐忑寫了一個測試代碼:
// 測試系統(tǒng) public class MiniDuckSimulator {public static void main(String[] args) {Duck mallardDuck = new MallardDuck(); // 綠頭鴨mallardDuck.display();mallardDuck.performFly();mallardDuck.performQuack();System.out.println("-----------------");Duck redheadDuck = new RedheadDuck();redheadDuck.display();redheadDuck.performFly();redheadDuck.performQuack();System.out.println("-----------------");Duck rubberDuck = new RubberDuck();rubberDuck.display();rubberDuck.performFly();rubberDuck.performQuack();System.out.println("-----------------");Duck modelDuck = new ModelDuck();modelDuck.display();modelDuck.performFly();modelDuck.performQuack();modelDuck.setFlyBehavior(new FlyNoWay()); // 動態(tài)改變對象行為modelDuck.performFly();} }輸出結(jié)果:
我是一只綠頭鴨!! I'm flying!!! 嘎嘎嘎 ----------------- 我是一只紅頭鴨!! I'm flying!!! 嘎嘎嘎 ----------------- 我是一只橡皮鴨!! I can't flying 吱吱吱 ----------------- 我是一只模型鴨!! Fly with a rocket!! 我不會叫 I can't flying第二版的代碼完美的實現(xiàn)了所有的功能,并且代碼的彈性和拓展性都很不錯,張三想:升職加薪這不就穩(wěn)穩(wěn)地嘛;
李四這時說:這個代碼就是用到了設(shè)計模式之一 ——策略模式,想要升職加薪,光會這一個設(shè)計模式可不行,后面的路還長著呢;
體會到了設(shè)計模式的好處,張三下定決心好好學習設(shè)計模式;
總結(jié)
上面張三的例子可以看出策略模式的好處
- 不需要許多 if …else或者switch 判斷語句
- 代碼可拓展性好
- 符合開閉原則,便于維護
同樣策略模式需要注意:每添加一個策略就要增加一個類,當策略過多是會導致策略類膨脹;
其實這個例子中還用到了一個設(shè)計原則: 多用組合和聚合,少用泛化(繼承)
這里總結(jié)一下文中提到的 三種設(shè)計原則:
- 封裝變化的行為
- 面向接口編程,不針對實現(xiàn)編程
- 多用組合聚合,少用繼承
當然這三個只是這里用到的,對于設(shè)計原則可不止這三種,后面會一 一介紹;
再重新看一下策略模式的定義:
策略模式是對算法的包裝,是把使用算法的責任和算法本身分割開來,委派給不同的對象管理。
策略模式結(jié)構(gòu)圖:
其實策略模式在Java源碼中也有體現(xiàn),簡單舉個例子:Constructor就用到了策略模式,我們可以通過實現(xiàn)它來創(chuàng)造不同的排序規(guī)則,感興趣可以看看源碼體驗一下;
當然一個例子不足以讓你會用策略模式,想要真正的掌握還是需要大量的練習和實踐,希望這篇文章能給你帶來一些啟發(fā)!
歡迎大家的點評!
總結(jié)
以上是生活随笔為你收集整理的设计模式(一)————策略模式(张三的故事??)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ❗HTML引入JavaScript的三种
- 下一篇: 数据结构解析——小白也能看懂的单链表