策略模式(Strategy Pattern)

一、策略

策略砸泛,指的是可以實(shí)現(xiàn)目標(biāo)的方案集合十籍,在某些特定情況下,策略之間是可以相互替換的唇礁。

比如在外賣平臺上的這些優(yōu)惠勾栗。滿減、會員和紅包等盏筐,每一個大項(xiàng)優(yōu)惠都具體包含了多個優(yōu)惠方案围俘。如滿減活動中,可以同時有滿28減18琢融、滿58減38等界牡。會員包含普通會員、超級會員等漾抬。

每一個優(yōu)惠方式下面的多個優(yōu)惠方案欢揖,其實(shí)都是一個策略。這些策略之間是相互排斥奋蔚、可替換的她混。并且是有一定的優(yōu)先級順序的。

二泊碑、業(yè)務(wù)場景理解

拿點(diǎn)外賣中會員折扣活動舉例子來說坤按,平臺商家為了促銷,設(shè)置多種會員優(yōu)惠馒过,包含超級會員8折臭脓、普通會員9折和普通用戶沒有折扣三種。用戶在付款的時候腹忽,平臺根據(jù)用戶的會員等級来累,就可以知道用戶符合哪種折扣策略,進(jìn)而計(jì)算出應(yīng)付金額窘奏。代碼中如下:

public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {
    if (BuyerType.SVIP.name.equals(buyerType)) {
        return orderPrice.multiply(new BigDecimal(0.8));
    }
    if (BuyerType.VIP.name.equals(buyerType)) {
        return orderPrice.multiply(new BigDecimal(0.9));
    }
    return orderPrice;
}

代碼比較簡單嘹锁,就是在代碼中通過if-else進(jìn)行邏輯判斷,不同類型的會員享受不同的折扣價(jià)着裹。

此時领猾,平臺增加了一種店鋪專屬會員,這種會員可以專享某一個店鋪菜品的7折優(yōu)惠,代碼需要改成如下:

public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {
    if (BuyerType.EXCLUSIVE_VIP.name.equals(buyerType)) {
        return orderPrice.multiply(new BigDecimal(0.7));
    }
    if (BuyerType.SVIP.name.equals(buyerType)) {
        return orderPrice.multiply(new BigDecimal(0.8));
    }
    if (BuyerType.VIP.name.equals(buyerType)) {
        return orderPrice.multiply(new BigDecimal(0.9));
    }
    return orderPrice;
}

而后摔竿,隨著業(yè)務(wù)發(fā)展面粮,新的需求要求專屬會員要在店鋪下單金額大于28元的時候才可以享受優(yōu)惠。代碼就需要再次修改:

public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {
    if (BuyerType.EXCLUSIVE_VIP.name.equals(buyerType)) {
        if(orderPrice.compareTo(new BigDecimal(28))>0){
            return orderPrice.multiply(new BigDecimal(0.7));
        }
    }
    if (BuyerType.SVIP.name.equals(buyerType)) {
        return orderPrice.multiply(new BigDecimal(0.8));
    }
    if (BuyerType.VIP.name.equals(buyerType)) {
        return orderPrice.multiply(new BigDecimal(0.9));
    }
    return orderPrice;
}

接著继低,又有一個復(fù)雜的需求熬苍,如果用戶的會員已經(jīng)到期了,并且到期時間在一周內(nèi)袁翁,那么就對用戶的單筆訂單按照超級會員進(jìn)行折扣柴底,并在收銀臺進(jìn)行強(qiáng)提醒,引導(dǎo)用戶再次開通會員梦裂,而且折扣只進(jìn)行一次。代碼需要做如下修改:

public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {
    if (BuyerType.EXCLUSIVE_VIP.name.equals(buyerType)) {
        if (orderPrice.compareTo(new BigDecimal(28)) > 0) {
            return orderPrice.multiply(new BigDecimal(0.7));
        }
    }
    if (BuyerType.SVIP.name.equals(buyerType)) {
        return orderPrice.multiply(new BigDecimal(0.8));
    }
    if (BuyerType.VIP.name.equals(buyerType)) {
        //過期天數(shù)
        int vipExpireDays = getVipExpireDays();
        //過期使用次數(shù)為0
        int vipTime = getVipTime();
        if (vipExpireDays < 7 && vipTime == 0) {
            //更新過期使用次數(shù)
            updatevipExpireUsed();
            return orderPrice.multiply(new BigDecimal(0.8));
        }
        return orderPrice.multiply(new BigDecimal(0.9));
    }
    return orderPrice;
}

三盖淡、策略模式應(yīng)用

以上代碼年柠,所有關(guān)于會員折扣的代碼全部都寫在了一個方法中,增加或者減少一種會員類型褪迟,就需要改動到整個方法冗恨。還要考慮這種會員折扣的優(yōu)先級問題。除了增加會員類型外味赃,其中任何一種會員類型的折扣策略發(fā)生變化掀抹,也需要改動到整個算法。這就會導(dǎo)致這個算法越來越臃腫心俗,進(jìn)而得到的后果就是代碼行數(shù)越來越多傲武,開發(fā)人員改動一點(diǎn)點(diǎn)代碼都需要把所有功能全部都回歸一遍。就比如說只是把超級會員的折扣從8折改為8.5折城榛,這時候因?yàn)榇a都在一起揪利,那就需要在上線的時候?qū)τ跁T折扣的所有功能進(jìn)行回歸。久而久之狠持,代碼可讀性疟位、可維護(hù)性、可擴(kuò)展性均變得極低喘垂,并且回歸成本較高甜刻。

1??策略模式

日常生活中,要實(shí)現(xiàn)目標(biāo)正勒,有很多方案得院,每一個方案都被稱之為一個策略。在軟件開發(fā)中也常常遇到類似的情況章贞,實(shí)現(xiàn)某一個功能有多個途徑尿招,此時可以使用一種設(shè)計(jì)模式來使得系統(tǒng)可以靈活地選擇解決途徑,也能夠方便地增加新的解決途徑。這就是策略模式就谜。

策略模式怪蔑,指的是定義一系列算法,將每一個算法封裝起來丧荐,并讓它們可以相互替換缆瓣。策略模式讓算法獨(dú)立于使用它的客戶而變化。

特別說明一下虹统,策略模式只適用管理一組同類型的算法弓坞,并且這些算法是完全互斥的情況。也就是說任何時候车荔,多個策略中只有一個可以生效的那一種渡冻。如滿減中的滿28減18與滿58減38之間;普通會員折扣與超級會員折扣之間等忧便。

在策略模式中族吻,定義一些獨(dú)立的類來封裝不同的算法,每一個類封裝一個具體的算法珠增,在這里超歌,每一個封裝算法的類都可以稱之為策略,為了保證這些策略的一致性蒂教,一般會用一個抽象的策略類來做算法的定義巍举,而具體每種算法則對應(yīng)于一個具體策略類。

要實(shí)現(xiàn)策略模式凝垛,肯定離不開策略懊悯。如前面提到的超級會員、普通會員梦皮、專屬會員等的折扣其實(shí)都是策略定枷。完全可以通過策略模式來實(shí)現(xiàn)。實(shí)現(xiàn)策略模式主要包含的角色如下:

2??抽象策略類

先定義一個接口届氢,這個接口就是抽象策略類欠窒,該接口定義了計(jì)算價(jià)格方法,具體實(shí)現(xiàn)方式由具體的策略類來定義退子。

public interface Buyer {
    BigDecimal calPrice(BigDecimal orderPrice);
}

3??具體策略類

針對不同的會員岖妄,定義三種具體的策略類,每個類中都分別實(shí)現(xiàn)計(jì)算價(jià)格方法寂祥。

public class ExclusiveVIP implements Buyer {
    @Override
    public BigDecimal calPrice(BigDecimal orderPrice) {
        if (orderPrice.compareTo(new BigDecimal(28)) > 0) {
            return orderPrice.multiply(new BigDecimal(0.7));
        }
        return orderPrice;
    }
}
public class SVIP implements Buyer {
    @Override
    public BigDecimal calPrice(BigDecimal orderPrice) {
        return orderPrice.multiply(new BigDecimal(0.8));
    }
}
public class VIP implements Buyer {
    @Override
    public BigDecimal calPrice(BigDecimal orderPrice) {
        //過期天數(shù)
        int vipExpireDays = getVipExpireDays();
        //過期使用次數(shù)為0
        int vipTime = getVipTime();
        if (vipExpireDays < 7 && vipTime == 0) {
            //更新過期使用次數(shù)
            updatevipExpireUsed();
            return orderPrice.multiply(new BigDecimal(0.8));
        }
        return orderPrice.multiply(new BigDecimal(0.9));
    }
}

上面幾個類的定義體現(xiàn)了封裝變化的設(shè)計(jì)原則荐虐,不同會員的具體折扣方式改變不會影響到其他的會員。

4??上下文類

定義好了抽象策略類和具體策略類之后丸凭,再來定義上下文類福扬,所謂上下文類腕铸,就是集成算法的類。這個例子中就是收銀臺系統(tǒng)铛碑。采用組合的方式把會員集成進(jìn)來狠裹。

public class Cashier {
    private Buyer buyer;
    public Cashier(Buyer buyer) {
        this.buyer = buyer;
    }
    public BigDecimal qupta(BigDecimal orderPrice) {
        return this.buyer.calPrice(orderPrice);
    }
}

這個 Cashier 類就是一個上下文類,該類的定義體現(xiàn)了多用組合汽烦,少用繼承涛菠。針對接口,不針對實(shí)現(xiàn)兩個設(shè)計(jì)原則撇吞。

由于這里采用了組合+接口的方式俗冻,后面再推出其他類型會員的時候無須修改 Cashier 類。只要再定義一個類實(shí)現(xiàn) Buyer 接口就可以了牍颈。

除了增加會員類型以外迄薄,想要修改某個會員的折扣情況的時候,只需要修改該會員對應(yīng)的策略類就可以了煮岁,不需要修改到其他的策略讥蔽。也就控制了變更的范圍。大大降低了成本人乓。

5??測試類

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.math.BigDecimal;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DemoApplication.class})
public class TradeTest {
    @Test
    public void testCash() {

        Cashier ex = new Cashier(new ExclusiveVIP());
        System.out.println(ex.quota(new BigDecimal(100)));

        Cashier svip = new Cashier(new SVIP());
        System.out.println(svip.quota(new BigDecimal(100)));

        Cashier vip = new Cashier(new VIP());
        System.out.println(vip.quota(new BigDecimal(100)));
    }
}

由示例可知勤篮,策略模式僅僅封裝算法都毒,提供新的算法插入到已有系統(tǒng)中,策略模式并不決定在何時使用何種算法。在什么情況下使用什么算法是由客戶端決定的袋马。

四非凌、策略模式的優(yōu)缺點(diǎn)

策略模式可以充分的體現(xiàn)面向?qū)ο笤O(shè)計(jì)原則中的封裝變化、多用組合瀑焦,少用繼承腌且、針對接口編程,不針對實(shí)現(xiàn)編程等原則榛瓮。

策略模式具有以下特點(diǎn):

1??策略模式的關(guān)注點(diǎn)不是如何實(shí)現(xiàn)算法铺董,而是如何組織、調(diào)用這些算法禀晓,從而讓程序結(jié)構(gòu)更靈活精续,具有更好的維護(hù)性和擴(kuò)展性。
2??策略模式中各個策略算法是平等的粹懒。對于一系列具體的策略算法重付,地位是完全一樣的。正因?yàn)檫@個平等性凫乖,才能實(shí)現(xiàn)算法之間可以相互替換确垫。所有的策略算法在實(shí)現(xiàn)上也是相互獨(dú)立的弓颈,相互之間是沒有依賴的。所以可以這樣描述這一系列策略算法:策略算法是相同行為的不同實(shí)現(xiàn)删掀。
3??運(yùn)行期間翔冀,策略模式在每一個時刻只能使用一個具體的策略實(shí)現(xiàn)對象,雖然可以動態(tài)地在不同的策略實(shí)現(xiàn)中切換爬迟,但是同時只能使用一個橘蜜。

如果所有的具體策略類都有一些公有的行為。這時候付呕,就應(yīng)當(dāng)把這些公有的行為放到共同的抽象策略角色 Strategy 類里面计福。當(dāng)然這時候抽象策略角色必須要用 Java 抽象類實(shí)現(xiàn),而不能使用接口徽职。但是象颖,編程中沒有銀彈,策略模式也不例外姆钉,也有一些缺點(diǎn)说订,先來回顧總結(jié)下優(yōu)點(diǎn):

1??策略模式提供了對“開閉原則”的完美支持,用戶可以在不修改原有系統(tǒng)的基礎(chǔ)上選擇算法或行為潮瓶,也可以靈活地增加新的算法或行為陶冷。
2??策略模式提供了管理相關(guān)的算法族的辦法。策略類的等級結(jié)構(gòu)定義了一個算法或行為族毯辅。恰當(dāng)使用繼承可以把公共的代碼移到父類里面埂伦,從而避免代碼重復(fù)。
3??使用策略模式可以避免使用多重條件(if-else)語句思恐。多重條件語句不易維護(hù)沾谜,它把采取哪一種算法或采取哪一種行為的邏輯與算法或行為的邏輯混合在一起,統(tǒng)統(tǒng)列在一個多重條件語句里面胀莹,比使用繼承的辦法還要原始和落后基跑。

但同時,也有如下缺點(diǎn):

1??客戶端必須知道所有的策略類描焰,并自行決定使用哪一個策略類媳否。這就意味著客戶端必須理解這些算法的區(qū)別,以便適時選擇恰當(dāng)?shù)乃惴惥G亍_@種策略類的創(chuàng)建及選擇其實(shí)也可以通過工廠模式來輔助進(jìn)行篱竭。
2??由于策略模式把每個具體的策略實(shí)現(xiàn)都單獨(dú)封裝成為類,如果備選的策略很多的話萄凤,那么對象的數(shù)目就會很可觀室抽。可以通過使用享元模式在一定程度上減少對象的數(shù)量靡努。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坪圾,一起剝皮案震驚了整個濱河市晓折,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兽泄,老刑警劉巖漓概,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異病梢,居然都是意外死亡胃珍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蜓陌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來觅彰,“玉大人,你說我怎么就攤上這事钮热√钐В” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵隧期,是天一觀的道長飒责。 經(jīng)常有香客問我,道長仆潮,這世上最難降的妖魔是什么宏蛉? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮性置,結(jié)果婚禮上拾并,老公的妹妹穿的比我還像新娘。我一直安慰自己蚌讼,他們只是感情好辟灰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布个榕。 她就那樣靜靜地躺著篡石,像睡著了一般。 火紅的嫁衣襯著肌膚如雪西采。 梳的紋絲不亂的頭發(fā)上凰萨,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機(jī)與錄音械馆,去河邊找鬼胖眷。 笑死,一個胖子當(dāng)著我的面吹牛霹崎,可吹牛的內(nèi)容都是我干的珊搀。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼尾菇,長吁一口氣:“原來是場噩夢啊……” “哼境析!你這毒婦竟也來了囚枪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤劳淆,失蹤者是張志新(化名)和其女友劉穎链沼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沛鸵,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡括勺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了曲掰。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疾捍。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖栏妖,靈堂內(nèi)的尸體忽然破棺而出拾氓,到底是詐尸還是另有隱情,我是刑警寧澤底哥,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布咙鞍,位于F島的核電站,受9級特大地震影響趾徽,放射性物質(zhì)發(fā)生泄漏续滋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一孵奶、第九天 我趴在偏房一處隱蔽的房頂上張望疲酌。 院中可真熱鬧,春花似錦了袁、人聲如沸朗恳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粥诫。三九已至,卻和暖如春崭庸,著一層夾襖步出監(jiān)牢的瞬間怀浆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工怕享, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留执赡,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓函筋,卻偏偏與公主長得像沙合,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子跌帐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

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