生活中的設(shè)計(jì)模式之策略模式

定義

defines a family of functionality, encapsulate each one, and make them interchangeable

定義一個(gè)抽象的算法類坤次,將具體的算法封裝成單獨(dú)的對象蝴光,使它們可以相互替換睹耐。

實(shí)列

無論是生活中還是程序中,我們都會面臨許許多多的問題舌胶,而解決問題的方式或方法往往又有很多棉安,這時(shí)就需要我們根據(jù)具體情況選擇一個(gè)合適的方式解決問題气破。
比如說请垛,每天上下班你都會面臨如何到達(dá)目的地的問題,是步行還是開車亦或是坐公交见咒,你都需要做出權(quán)衡取舍偿衰。同樣,程序中如果你要給一串無序的數(shù)字排序改览,那么你有冒泡排序下翎、快速排序、插入排序等等排序算法可以選者恃疯;
還有漏设,如果你要給一串字符加密,那么你同樣會有很多算法可選如:MD5今妄、SHA1郑口、DES。上面的例子都可以使用策略模式來實(shí)現(xiàn)盾鳞,接下來我們看看如何正確的使用策略模式犬性。

小故事

一周前,我?guī)屠习彘_發(fā)了一款計(jì)算稅后收入的軟件腾仅,當(dāng)時(shí)只能計(jì)算工資乒裆。
前天,他期望這軟件也能計(jì)算年終獎推励,因?yàn)楣べY和年終獎的算法不一樣鹤耍,所以當(dāng)天我就增加好了。
今天验辞,又和我說要增加稿黄,利息......,天知道他要加多少種跌造。

public class Calculator {

    public Integer getRealIncome(Integer amount,String type){
        Integer incomeTax= 0;

        if("工資".equals(type)){
            //......計(jì)算工資所得稅的算法
        }else if("年終獎".equals(type)){
            //......計(jì)算年終獎所得稅的算法
        }else if("利息".equals(type)){
            //......計(jì)算利息所得稅的算法
        }
        //稅后收入
        return amount-incomeTax;
    }

}

問題

故事中杆怕,有三種類型的收入:工資族购、年終獎、利息陵珍,分別對應(yīng)三種不同的算法寝杖。之后,很可能還會要求增加股息互纯、紅利等算法瑟幕,且不說這么多的算法耦合在一個(gè)上下文(IncomeTaxCalculator)中,會造成代碼膨脹留潦、難以維護(hù)收苏,
而且還會讓代碼失去擴(kuò)展性、穩(wěn)定性愤兵。比如說,你的同事接手了你的代碼排吴,他想不修改你代碼的前提下秆乳,給這個(gè)軟件擴(kuò)展一種新的收入類型,那該如何實(shí)現(xiàn)呢钻哩?
又或者屹堰,他開發(fā)的算法性能更好,想替換你的算法或者兩個(gè)都同時(shí)保留由客戶端根據(jù)具體情況進(jìn)行切換街氢,那又該怎么辦扯键?
最后,如果收入類型有很多且數(shù)量不固定珊肃,真這樣不停的用條件語句進(jìn)行擴(kuò)展荣刑,難免會殃及池魚。

因此伦乔,這種情況下我們應(yīng)該使用更具有擴(kuò)展性的方式——策略模式厉亏。

方案

策略模式,首先會將算法從上下文(Context)中提取出來并封裝到單獨(dú)的類中烈和,這個(gè)類被稱為策略類(Strategy)爱只;然后會維護(hù)一個(gè)指向策略對象的引用,并向客戶端暴露設(shè)置該引用的操作招刹,使客戶端在運(yùn)行時(shí)可以動態(tài)的切換策略恬试。
這樣,擴(kuò)展或修改算法時(shí)疯暑,只需新增或修改策略類训柴,而不是修改上下文的代碼。如此這般缰儿,也就不會影響其它算法畦粮,造成代碼膨脹、難以維護(hù)。
另外宣赔,客戶端可以在運(yùn)行時(shí)预麸,根據(jù)具體情況動態(tài)的切換策略;因?yàn)槿褰呗詫ο笫怯煽蛻舳藙?chuàng)建并傳遞給上下文的吏祸,上下文根本不知道算法的實(shí)現(xiàn)細(xì)節(jié)甚至連具體是哪一個(gè)策略都不知道,關(guān)于算法它只負(fù)責(zé)將計(jì)算任務(wù)委派給策略钩蚊,其它一概不知贡翘。

應(yīng)用

接下來,我們使用策略模式重構(gòu)一下"計(jì)算稅后收入的軟件"砰逻。

首先鸣驱,創(chuàng)建抽象的所得稅計(jì)算策略,它接收一個(gè)金額并返回該金額所要交納的所得稅蝠咆。

/**抽象所得稅計(jì)算策略*/
public interface TaxStrategy {

    public Integer calculate(Integer amount);
}

然后踊东,創(chuàng)建各種收入類型對應(yīng)的策略類,它是抽象策略類的子類刚操,負(fù)責(zé)實(shí)現(xiàn)具體的算法闸翅。


/**工資所得稅計(jì)算策略*/
public class SalaryStrategy implements TaxStrategy {
    @Override
    public Integer calculate(Integer amount) {
        System.out.println("計(jì)算薪資所得稅");
    }
}

/**利息所得稅計(jì)算策略*/
public class InterestStrategy implements TaxStrategy {
    @Override
    public Integer calculate(Integer amount) {
        System.out.println("計(jì)算利息所得稅");
    }
}

/**年終獎所得稅計(jì)算策略*/
public class YearEndBonusStrategy implements TaxStrategy {
    @Override
    public Integer calculate(Integer amount) {
        System.out.println("計(jì)算薪資所得稅");
    }
}


現(xiàn)在,我們修改一下上下文類Calculator菊霜,讓它使用策略來計(jì)算各種收入的所得稅坚冀。


/**個(gè)稅計(jì)算器*/
public class Calculator {

    protected TaxStrategy taxStrategy;

    public void setTaxStrategy(TaxStrategy taxStrategy) {
        this.taxStrategy = taxStrategy;
    }



    public Integer getRealIncome(Integer amount){

        Integer incomeTax = taxStrategy.calculate(amount);
        //稅后收入
        return amount-incomeTax;
    }

}

最后,我們在看看客戶端如何動態(tài)切換算法鉴逞。


public class Client {

    public static void main(String[] args) {
        Calculator calculator = new Calculator();

        //計(jì)算年終獎的收入
        calculator.setTaxStrategy(new YearEndBonusStrategy());
        Integer bonusIncome = calculator.getRealIncome(10000);

        /**動態(tài)切換策略*/

        //用性能更好的算法計(jì)算年終獎
        calculator.setTaxStrategy(new YearEndBonusV2Strategy());
        Integer bonus = calculator.getRealIncome(10000);

    }
}

結(jié)構(gòu)

avatar

抽象策略角色(Strategy) :聲明所有具體算法的共同接口记某,它是對上下文對象中共同行為的抽象。

具體策略角色(ConcreteStrategy):實(shí)現(xiàn)抽象策略接口构捡,負(fù)責(zé)實(shí)現(xiàn)具體的算法辙纬,算法細(xì)節(jié)對上下文對象不可見。

上下文角色(Context) :具體策略的觸發(fā)類叭喜,它持有一個(gè)指向Strategy抽象類的引用贺拣,并向客戶端暴露設(shè)置策略的操作,允許客戶端動態(tài)地改變策略捂蕴。

客戶端(Client) :上下文的使用類譬涡,它根據(jù)具體情況決定使用哪一個(gè)策略,然后傳遞給上下文對象啥辨。

/**抽象策略類*/
public interface Strategy {
    public void operation();
}

/**具體策略類A*/
public class ConcreteStrategyA implements Strategy{
    @Override
    public void operation() {
        System.out.println("ConcreteStrategyA");
    }
}

/**具體策略類B*/
public class ConcreteStrategyB implements Strategy{
    @Override
    public void operation() {
        System.out.println("ConcreteStrategyB");
    }
}

/**上下文類*/
public class Context {
    protected Strategy strategy;

    public void setStrategy(Strategy strategy){
        this.strategy = strategy;
    }

    public void doSomeThing(){
        System.out.println("before operation");
        strategy.operation();
        System.out.println("after operation");
    }
}

public class Client {

    public static void main(String[] args) {
        Context context = new Context();
        //使用策略A
        context.setStrategy(new ConcreteStrategyA());
        context.doSomeThing();

        //動態(tài)切換策略
        context.setStrategy(new ConcreteStrategyB());
        context.doSomeThing();
    }
}


總結(jié)

當(dāng)一個(gè)對象的某個(gè)行為存在多種實(shí)現(xiàn)時(shí)涡匀,那么將它們封裝成一個(gè)單獨(dú)的對象,并使它們同屬于一個(gè)繼承等級結(jié)構(gòu)溉知。
這樣陨瘩,可以使上下文類不關(guān)心具體的實(shí)現(xiàn)細(xì)節(jié)以及一致地使用這些策略腕够,也能讓客戶端在運(yùn)行時(shí)動態(tài)的改變策略,還能在不修改上下文類的前提下擴(kuò)展策略舌劳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末帚湘,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子甚淡,更是在濱河造成了極大的恐慌大诸,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贯卦,死亡現(xiàn)場離奇詭異资柔,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)撵割,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門贿堰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人啡彬,你說我怎么就攤上這事官边。” “怎么了外遇?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長契吉。 經(jīng)常有香客問我跳仿,道長,這世上最難降的妖魔是什么捐晶? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任菲语,我火速辦了婚禮,結(jié)果婚禮上惑灵,老公的妹妹穿的比我還像新娘山上。我一直安慰自己,他們只是感情好英支,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布佩憾。 她就那樣靜靜地躺著,像睡著了一般干花。 火紅的嫁衣襯著肌膚如雪妄帘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天池凄,我揣著相機(jī)與錄音抡驼,去河邊找鬼。 笑死肿仑,一個(gè)胖子當(dāng)著我的面吹牛致盟,可吹牛的內(nèi)容都是我干的碎税。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼馏锡,長吁一口氣:“原來是場噩夢啊……” “哼雷蹂!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起眷篇,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤萎河,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后蕉饼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虐杯,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年昧港,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了擎椰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡创肥,死狀恐怖达舒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情叹侄,我是刑警寧澤巩搏,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站趾代,受9級特大地震影響贯底,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜撒强,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一禽捆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧飘哨,春花似錦胚想、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至胚吁,卻和暖如春臼闻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背囤采。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工述呐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蕉毯。 一個(gè)月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓乓搬,卻偏偏與公主長得像思犁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子进肯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評論 2 345

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