實(shí)現(xiàn)動(dòng)態(tài)替換-策略模式

模式介紹

通常勺届,我們實(shí)現(xiàn)一個(gè)功能驶俊,可以有多種策略。
我們可以根據(jù)實(shí)際需求來選擇最合適的策略免姿。
針對(duì)這種情況饼酿,一種常規(guī)的方法就是將多種策略封裝在一個(gè)類中,每個(gè)策略對(duì)應(yīng)一個(gè)方法胚膊,在使用時(shí)通過if-else進(jìn)行情況判斷嗜湃,不同情況調(diào)用不同方法來實(shí)現(xiàn)不同的策略奈应。
這種實(shí)現(xiàn)方法我們可以稱之為硬編碼。

然而购披,當(dāng)策略越來越多的時(shí)候杖挣,這個(gè)類就會(huì)變得臃腫,并且由于if-else等復(fù)雜邏輯的存在刚陡,在維護(hù)時(shí)會(huì)更容易產(chǎn)生錯(cuò)誤惩妇,維護(hù)成本就會(huì)變高。
并且這種寫法明顯違反了面向?qū)ο?/a>中的開閉原則筐乳。
如果我們將這些策略的共同點(diǎn)抽象出來歌殃,提供一個(gè)統(tǒng)一的接口,不同的策略有著不同的實(shí)現(xiàn)蝙云,這樣在客戶端就可以通過注入不同的實(shí)現(xiàn)對(duì)象來實(shí)現(xiàn)動(dòng)態(tài)替換氓皱。
這種模式的可擴(kuò)展性、可維護(hù)性都是極高的勃刨。這波材,就是我們要說的策略模式
總感覺身隐,這模式廷区,不就是多態(tài)嘛。贾铝。

模式定義

針對(duì)一個(gè)功能隙轻,將每一種解決方案封裝起來,而且使它們可以相互替換垢揩,這就是策略模式玖绿。

使用場(chǎng)景

  • 針對(duì)同一類型問題的多種處理方式,僅僅是具體行為存在差異叁巨;
  • 需要安全地封裝多種同一類型的操作時(shí)斑匪;
  • 同一抽象類有多個(gè)子類,同時(shí)又需要使用if-else或swithc-case來選擇具體子類時(shí)俘种。

模式角色

  • Strategy:策略抽象。
  • ConcreteStrategy:具體策略绝淡,針對(duì)一個(gè)問題宙刘,有多少種策略,就應(yīng)有多少個(gè)具體策略牢酵。

模式示例

2014年12月28日北京提高了公交悬包、地鐵的價(jià)格,不再是單一票價(jià)馍乙,而是分距離計(jì)價(jià)布近。
我們就根據(jù)上述需求垫释,來做一個(gè)出行價(jià)格計(jì)算器。
話不多說撑瞧,我們來實(shí)現(xiàn)這個(gè)功能:


測(cè)試UI

簡(jiǎn)單做了計(jì)算器的UI棵譬,它應(yīng)該提供具體出行方式的選擇,很明顯预伺,我們提供了三種订咸。
并且有一個(gè)出行距離的輸入框。
在獲取到出行方式和出行距離后酬诀,我們就可以來計(jì)算具體價(jià)格脏嚷。
需求很明確,接下來我們要做的就是先實(shí)現(xiàn)各種出行方式下的價(jià)格計(jì)算:

public class PriceCaculateController {

    /**
     * 根據(jù)出行方式瞒御,選擇距離的計(jì)算方法
     */
    public static float cacluatePrice(int km, int typeMode) {
        switch (typeMode) {
            case Constant.TYPE_BUS:
                return caculateBusPrice(km);
            case Constant.TYPE_SUBWAY:
                return caculateSubwayPrice(km);
            case Constant.TYPE_TAX:
                return caculateTaxPrice(km);
        }
        return 0;
    }

    /**
     * 計(jì)算出租車價(jià)格
     * 小于3Km父叙,定價(jià)9元,大于3km肴裙,每1km + 1元
     *
     */
    private static float caculateTaxPrice(int km) {
        if (km <= 0) {
            return 0;
        }
        if (km <= 3) {
            return 9;
        }
        return 9 + (km - 3);
    }

    /**
     * 計(jì)算公交車價(jià)格
     * 小于5Km趾唱,定價(jià)2元,小于10km践宴,定價(jià)3元鲸匿,其余4元
     */
    private static float caculateBusPrice(int km) {
        if (km <= 0) {
            return 0;
        }
        if (km <= 5) {
            return 2;
        }
        if (km <= 10) {
            return 3;
        }
        return 4;
    }
    /**
     * 計(jì)算地鐵價(jià)格
     * 小于5Km,定價(jià)3元阻肩,小于10km带欢,定價(jià)4元,小于15Km烤惊,定價(jià)5元乔煞,其余6元
     */
    private static float caculateSubwayPrice(int km) {
        if (km <= 0) {
            return 0;
        }
        if (km <= 5) {
            return 3;
        }
        if (km <= 10) {
            return 4;
        }
        if (km <= 15) {
            return 5;
        }
        return 6;
    }
}

PriceCaculateController中包含了所有出行方式的計(jì)算細(xì)節(jié)。
細(xì)心的同學(xué)可以發(fā)現(xiàn)柒室,我將PriceCaculateController中的所有計(jì)算方法私有化渡贾,向外暴露了一個(gè)cacluatePrice(int km, int typeMode)方法。
用戶在使用時(shí)雄右,僅需告訴我們出行距離與出行方式空骚,我們就可以完全自動(dòng)計(jì)算出價(jià)格,下面是使用的代碼:

float price = PriceCaculateController.cacluatePrice(20,1);

這樣我們就實(shí)現(xiàn)了頁面與計(jì)算功能的完美解耦擂仍,頁面完全不關(guān)心計(jì)算的細(xì)節(jié)囤屹。
接下來我們?cè)陧撁嫔咸砑踊A(chǔ)判斷之后,調(diào)用PriceCaculateController.cacluatePrice即可逢渔。
我們可以來看看具體效果:

具體效果

至此肋坚,我們的價(jià)格計(jì)算器已經(jīng)開發(fā)完成了。
很明顯,我們的核心代碼全在PriceCaculateController中智厌。
隨著出行方式的增加诲泌、價(jià)格計(jì)算的優(yōu)化,PriceCaculateController必定會(huì)變得臃腫不堪铣鹏。
為了將PriceCaculateController功能進(jìn)行拆分敷扫,我們就要使用今天講到的策略模式
讓我們來回顧策略模式中的角色:
一個(gè)策略抽象以及多個(gè)具體策略吝沫。

接下來我們就將價(jià)格計(jì)算功能進(jìn)行抽象:

public interface StrategyCaculate {

    /**
     * 根據(jù)距離計(jì)算價(jià)格
     */
    float caculatePrice(int km);
}

策略抽象非常簡(jiǎn)單呻澜。
接下里我們來實(shí)現(xiàn)具體策略,有多少種出行方式惨险,就應(yīng)該有多少種具體策略:
公共汽車(Bus):

public class BusStrategyCaculate implements StrategyCaculate {
    @Override
    public float caculatePrice(int km) {
        if (km <= 0) {
            return 0;
        }
        if (km <= 5) {
            return 2;
        }
        if (km <= 10) {
            return 3;
        }
        return 4;
    }
}

出租車(Tax):

public class TaxStrategyCaculate implements StrategyCaculate {
    @Override
    public float caculatePrice(int km) {
        if (km <= 0) {
            return 0;
        }
        if (km <= 3) {
            return 9;
        }
        return 9 + (km - 3);
    }
}

地鐵(Subway):

public class SubwayStrategyCaculate implements StrategyCaculate {
    @Override
    public float caculatePrice(int km) {
        if (km <= 0) {
            return 0;
        }
        if (km <= 5) {
            return 3;
        }
        if (km <= 10) {
            return 4;
        }
        if (km <= 15) {
            return 5;
        }
        return 6;
    }
}

三種出行方式對(duì)應(yīng)三個(gè)具體策略羹幸。
當(dāng)有新的出行方式時(shí),我們只需創(chuàng)建新的類去實(shí)現(xiàn)策略抽象即可辫愉。
簡(jiǎn)單寫一些測(cè)試代碼:

StrategyCaculate caculate = null;
switch (typeMode) {
    case Constant.TYPE_BUS:
        caculate = new BusStrategyCaculate();
        break;
    case Constant.TYPE_SUBWAY:
        caculate = new SubwayStrategyCaculate();
        break;
    case Constant.TYPE_TAX:
        caculate = new TaxStrategyCaculate();
        break;
}
assert caculate != null;
return caculate.caculatePrice(km);

可以發(fā)現(xiàn)栅受,這里我們又運(yùn)用多態(tài)的特性,根據(jù)不同的出行方式恭朗,創(chuàng)建不同的子類屏镊,接著調(diào)用子類中的價(jià)格計(jì)算。
最后的效果和之前的效果是一致的痰腮。
相關(guān)代碼已經(jīng)提交至GitHub而芥。

總結(jié)

有些人會(huì)有疑惑了,我們最開始的PriceCaculateController并不復(fù)雜膀值,并且一個(gè)方法對(duì)應(yīng)一種出行方式棍丐,清晰明了,為什么非要改成這個(gè)樣子?
對(duì)于這個(gè)問題沧踏,我們現(xiàn)在的出行方式僅僅有三種歌逢、并且價(jià)格計(jì)算的邏輯非常簡(jiǎn)單。
如果現(xiàn)在繼續(xù)添加出行方式:飛機(jī)翘狱、自駕秘案、高鐵、大巴等潦匈。
如果優(yōu)化出租車的價(jià)格計(jì)算邏輯:出租車3Km以內(nèi)9元阱高,之后每1Km加1元,燃?xì)赓M(fèi)2元茬缩,堵車服務(wù)費(fèi)每分鐘0.1元赤惊,夜晚11點(diǎn)之后價(jià)格提升,每個(gè)城市的價(jià)格不同等等寒屯。
如果將上述邏輯全部寫出荐捻,那么就出租車價(jià)格計(jì)算的邏輯,就要有上百行的代碼寡夹。
如果你們公司是一家專業(yè)的出行價(jià)格計(jì)算公司处面,這些計(jì)算細(xì)節(jié)、出行方式都必定要涵蓋到菩掏。
那么將來魂角,PriceCaculateController的代碼量,會(huì)有多大智绸?
如果我們運(yùn)用了策略模式野揪,項(xiàng)目的目錄結(jié)構(gòu)就變成了:

策略模式目錄結(jié)構(gòu)

當(dāng)某種交通工具的價(jià)格計(jì)算發(fā)生問題,我們僅僅去找對(duì)應(yīng)的具體策略即可瞧栗。并且避免產(chǎn)生了PriceCaculateController這種代碼量龐大的類辜梳。
策略模式很好地遵循了開閉原則取视,注入不同的具體策略,會(huì)有不同的效果,從而達(dá)到很好的擴(kuò)展性瞒大。
使用策略模式的優(yōu)點(diǎn)有很多:

  • 結(jié)構(gòu)清晰明了,使用簡(jiǎn)單厉斟;
  • 降低耦合度兼犯,擴(kuò)展方便;
  • 封裝徹底锤岸,數(shù)據(jù)更為安全竖幔。

所以,在你能預(yù)見到某個(gè)功能會(huì)有擴(kuò)展的需求時(shí)是偷,并且使用場(chǎng)景符合策略模式的使用場(chǎng)景拳氢,還是強(qiáng)烈建議你使用策略模式的。

感謝

《Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)》 何紅輝晓猛、關(guān)愛民 著

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末饿幅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子戒职,更是在濱河造成了極大的恐慌栗恩,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洪燥,死亡現(xiàn)場(chǎng)離奇詭異磕秤,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)捧韵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門市咆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人再来,你說我怎么就攤上這事蒙兰×琢觯” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵搜变,是天一觀的道長(zhǎng)采缚。 經(jīng)常有香客問我,道長(zhǎng)挠他,這世上最難降的妖魔是什么扳抽? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮殖侵,結(jié)果婚禮上贸呢,老公的妹妹穿的比我還像新娘。我一直安慰自己拢军,他們只是感情好楞陷,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著茉唉,像睡著了一般猜谚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赌渣,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天魏铅,我揣著相機(jī)與錄音,去河邊找鬼坚芜。 笑死览芳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸿竖。 我是一名探鬼主播沧竟,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼缚忧!你這毒婦竟也來了悟泵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤闪水,失蹤者是張志新(化名)和其女友劉穎糕非,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體球榆,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡朽肥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了持钉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衡招。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖每强,靈堂內(nèi)的尸體忽然破棺而出始腾,到底是詐尸還是另有隱情州刽,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布浪箭,位于F島的核電站怀伦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏山林。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一邢羔、第九天 我趴在偏房一處隱蔽的房頂上張望驼抹。 院中可真熱鬧,春花似錦拜鹤、人聲如沸框冀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽明也。三九已至,卻和暖如春惯裕,著一層夾襖步出監(jiān)牢的瞬間温数,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工蜻势, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撑刺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓握玛,卻偏偏與公主長(zhǎng)得像够傍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子挠铲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1 場(chǎng)景問題# 1.1 報(bào)價(jià)管理## 向客戶報(bào)價(jià)冕屯,對(duì)于銷售部門的人來講,這是一個(gè)非常重大拂苹、非常復(fù)雜的問題安聘,對(duì)不同的...
    七寸知架構(gòu)閱讀 5,054評(píng)論 9 62
  • 1 場(chǎng)景問題 1.1 報(bào)價(jià)管理 向客戶報(bào)價(jià),對(duì)于銷售部門的人來講瓢棒,這是一個(gè)非常重大搞挣、非常復(fù)雜的問題,對(duì)不同的客戶要...
    4e70992f13e7閱讀 3,071評(píng)論 2 16
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理音羞,服務(wù)發(fā)現(xiàn)囱桨,斷路器,智...
    卡卡羅2017閱讀 134,600評(píng)論 18 139
  • 日課133 時(shí)尚動(dòng)力學(xué) 收獲來源《流行制造者》(Hit markers :How tings become po...
    蘆權(quán)閱讀 590評(píng)論 2 1
  • 今天上午語文課嗅绰,我們學(xué)習(xí)的拼音舍肠,學(xué)的是單韻母a o e搀继。老師在黑板上把四線格畫出來,然后又把單韻母寫進(jìn)四線格中翠语。老...
    軒小豆閱讀 498評(píng)論 0 0