摘要:策略策略,指的是可以實(shí)現(xiàn)目標(biāo)的方案集合,在某些特定情況下,策略之間是可以相互替換的。如何計(jì)算金額我們先拿點(diǎn)外賣中會(huì)員折扣活動(dòng)舉例子來說明一下吧。這就是策略模式。策略模式提供了管理相關(guān)的算法族的辦法。
?
周末無事,窩在家里面看《權(quán)力的游戲第八季》,看的很是津津有味,雖然感覺有一點(diǎn)點(diǎn)要爛尾,但是我還是忍不住要去看到底誰可以坐上鐵王座。
女朋友在一旁點(diǎn)外賣,好像是在使用優(yōu)惠的時(shí)候遇到了一點(diǎn)點(diǎn)小問題。
策略
策略,指的是可以實(shí)現(xiàn)目標(biāo)的方案集合,在某些特定情況下,策略之間是可以相互替換的。
比如我們?cè)谕赓u平臺(tái)上看到的這些優(yōu)惠。滿減、會(huì)員和紅包等,每一個(gè)大項(xiàng)優(yōu)惠都具體包含了多個(gè)優(yōu)惠方案。如滿減活動(dòng)中,可以同時(shí)有滿20減15、滿50減30等。會(huì)員包含普通會(huì)員、超級(jí)會(huì)員等。
每一個(gè)優(yōu)惠方式下面的多個(gè)優(yōu)惠方案,其實(shí)都是一個(gè)策略。這些策略之間是相互排斥、可替換的。并且是有一定的優(yōu)先級(jí)順序的。
如上圖,一筆訂單中共使用到了4種優(yōu)惠,可以說我們組合使用了四種優(yōu)惠策略。
如何計(jì)算金額
我們先拿點(diǎn)外賣中會(huì)員折扣活動(dòng)舉例子來說明一下吧。外賣平臺(tái)上的某家店鋪為了促銷,設(shè)置了多種會(huì)員優(yōu)惠,其中包含超級(jí)會(huì)員折扣8折、普通會(huì)員折扣9折和普通用戶沒有折扣三種。
我們希望用戶在付款的時(shí)候,根據(jù)用戶的會(huì)員等級(jí),就可以知道用戶符合哪種折扣策略,進(jìn)而進(jìn)行打折,計(jì)算出應(yīng)付金額。
代碼中可以這樣寫:
public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {
if (BuyerType.SUPER_VIP.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)行邏輯判斷,不同類型的會(huì)員享受不同的折扣價(jià)。
再增加一種會(huì)員類型
這個(gè)時(shí)候,平臺(tái)增加了一種店鋪專屬會(huì)員,這種會(huì)員可以專享某一個(gè)店鋪菜品的7折優(yōu)惠,那么代碼又要改成以下這樣:
public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {
if (BuyerType.PARTICULARLY_VIP.name().equals(buyerType)) {
return orderPrice.multiply(new BigDecimal(0.7));
}
if (BuyerType.SUPER_VIP.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;
}
會(huì)員折扣變化
后面,隨著業(yè)務(wù)發(fā)展,新的需求要求專屬會(huì)員要在店鋪下單金額大于30元的時(shí)候才可以享受優(yōu)惠。代碼就需要再次修改:
public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {
if (BuyerType.PARTICULARLY_VIP.name().equals(buyerType)) {
if (orderPrice.compareTo(new BigDecimal(30)) > 0) {
return orderPrice.multiply(new BigDecimal(0.7));
}
}
if (BuyerType.SUPER_VIP.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;
}
接著,又有一個(gè){{BANNED}}的需求,如果用戶的超級(jí)會(huì)員已經(jīng)到期了,并且到期時(shí)間在一周內(nèi),那么就對(duì)用戶的單筆訂單按照超級(jí)會(huì)員進(jìn)行折扣,并在收銀臺(tái)進(jìn)行強(qiáng)提醒,引導(dǎo)用戶再次開通會(huì)員,而且折扣只進(jìn)行一次。代碼需要做如下修改:
public BigDecimal calPrice(BigDecimal orderPrice, String buyerType) {
if (BuyerType.PARTICULARLY_VIP.name().equals(buyerType)) {
if (orderPrice.compareTo(new BigDecimal(30)) > 0) {
return orderPrice.multiply(new BigDecimal(0.7));
}
}
if (BuyerType.SUPER_VIP.name().equals(buyerType)) {
return orderPrice.multiply(new BigDecimal(0.8));
}
if (BuyerType.VIP.name().equals(buyerType)) {
int superVipExpiredDays = getSuperVipExpiredDays();
int superVipLeadDiscountTimes = getSuperVipLeadDiscountTimes();
if(superVipExpiredDays < 7 && superVipLeadDiscountTimes =0){
updateSuperVipLeadDiscountTimes();
return orderPrice.multiply(new BigDecimal(0.8));
}
return orderPrice.multiply(new BigDecimal(0.9));
}
return orderPrice;
}
為什么要使用策略模式
以上代碼,所有關(guān)于會(huì)員折扣的代碼全部都寫在了一個(gè)calPrice方法中,增加或者減少一種會(huì)員類型,就需要改動(dòng)到整個(gè)方法。還要考慮這種會(huì)員折扣的優(yōu)先級(jí)問題。
除了增加會(huì)員類型外,其中任何一種會(huì)員類型的折扣策略發(fā)生變化,也需要改動(dòng)到整個(gè)算法。
這就會(huì)導(dǎo)致這個(gè)算法越來越臃腫,進(jìn)而得到的后果就是代碼行數(shù)越來越多,開發(fā)人員改動(dòng)一點(diǎn)點(diǎn)代碼都需要把所有功能全部都回歸一遍。
就比如說我只是把超級(jí)會(huì)員的折扣從8折改為8.5折,這時(shí)候因?yàn)榇a都在一起,那就需要在上線的時(shí)候?qū)τ跁?huì)員折扣的所有功能進(jìn)行回歸。
?久而久之,這段代碼就變成了一段誰都不愿改,誰都不敢改的代碼。俗稱"屎山"。這種代碼會(huì)使代碼有極低的可讀性、可維護(hù)性、可擴(kuò)展性,并且回歸成本較高。
策略模式
我們說日常生活中,我們要實(shí)現(xiàn)目標(biāo),有很多方案,每一個(gè)方案都被稱之為一個(gè)策略。在軟件開發(fā)中也常常遇到類似的情況,實(shí)現(xiàn)某一個(gè)功能有多個(gè)途徑,此時(shí)可以使用一種設(shè)計(jì)模式來使得系統(tǒng)可以靈活地選擇解決途徑,也能夠方便地增加新的解決途徑。這就是策略模式。
策略模式(Strategy Pattern),指的是定義一系列算法,將每一個(gè)算法封裝起來,并讓它們可以相互替換。策略模式讓算法獨(dú)立于使用它的客戶而變化。
特別說明一下,策略模式只適用管理一組同類型的算法,并且這些算法是完全互斥的情況。也就是說任何時(shí)候,多個(gè)策略中只有一個(gè)可以生效的那一種。如滿減中的滿20減10與滿30減20之間;普通會(huì)員折扣與超級(jí)會(huì)員折扣之間等。
在策略模式中,定義一些獨(dú)立的類來封裝不同的算法,每一個(gè)類封裝一個(gè)具體的算法,在這里,每一個(gè)封裝算法的類我們都可以稱之為策略(Strategy),為了保證這些策略的一致性,一般會(huì)用一個(gè)抽象的策略類來做算法的定義,而具體每種算法則對(duì)應(yīng)于一個(gè)具體策略類。
要實(shí)現(xiàn)策略模式,肯定離不開策略。如前面提到的超級(jí)會(huì)員、普通會(huì)員、專屬會(huì)員等的折扣其實(shí)都是策略。完全可以通過策略模式來實(shí)現(xiàn)。
實(shí)現(xiàn)策略模式主要包含的角色如下:
抽象策略類
先定義一個(gè)接口,這個(gè)接口就是抽象策略類,該接口定義了計(jì)算價(jià)格方法,具體實(shí)現(xiàn)方式由具體的策略類來定義。
public interface Buyer { /** * 計(jì)算應(yīng)付價(jià)格 */ public BigDecimal calPrice(BigDecimal orderPrice); }
具體策略類
針對(duì)不同的會(huì)員,定義三種具體的策略類,每個(gè)類中都分別實(shí)現(xiàn)計(jì)算價(jià)格方法。
/**
* 專屬會(huì)員
*/
public class ParticularlyVipBuyer implements Buyer {
@Override
public BigDecimal calPrice(BigDecimal orderPrice) {
if (orderPrice.compareTo(new BigDecimal(30)) > 0) {
return orderPrice.multiply(new BigDecimal(0.7));
}
}
}
/**
* 超級(jí)會(huì)員
*/
public class SuperVipBuyer implements Buyer {
@Override
public BigDecimal calPrice(BigDecimal orderPrice) {
return orderPrice.multiply(new BigDecimal(0.8));
}
}
/**
* 普通會(huì)員
*/
public class VipBuyer implements Buyer {
@Override
public BigDecimal calPrice(BigDecimal orderPrice) {
int superVipExpiredDays = getSuperVipExpiredDays();
int superVipLeadDiscountTimes = getSuperVipLeadDiscountTimes();
if(superVipExpiredDays < 7 && superVipLeadDiscountTimes =0){
return orderPrice.multiply(new BigDecimal(0.8));
}
return orderPrice.multiply(new BigDecimal(0.9));
}
}
上面幾個(gè)類的定義體現(xiàn)了封裝變化的設(shè)計(jì)原則,不同會(huì)員的具體折扣方式改變不會(huì)影響到其他的會(huì)員。
定義好了抽象策略類和具體策略類之后,我們?cè)賮矶x上下文類,所謂上下文類,就是集成算法的類。這個(gè)例子中就是收銀臺(tái)系統(tǒng)。采用組合的方式把會(huì)員集成進(jìn)來。
public class Cashier {
/**
* 會(huì)員,策略對(duì)象
*/
private Buyer buyer;
public Cashier(Buyer buyer){
buyer = buyer;
}
public BigDecimal quote(BigDecimal orderPrice) {
return this.buyer.calPrice(orderPrice);
}
}
這個(gè)Cashier類就是一個(gè)上下文類,該類的定義體現(xiàn)了多用組合,少用繼承、針對(duì)接口編程,不針對(duì)實(shí)現(xiàn)編程兩個(gè)設(shè)計(jì)原則。
由于這里采用了組合+接口的方式,后面我們?cè)谕瞥銎渌愋蜁?huì)員的時(shí)候無須修改Cashier類。只要再定義一個(gè)類實(shí)現(xiàn)Buyer接口 就可以了。
除了增加會(huì)員類型以外,我們想要修改某個(gè)會(huì)員的折扣情況的時(shí)候,只需要修改該會(huì)員對(duì)應(yīng)的策略類就可以了,不需要修改到其他的策略。也就控制了變更的范圍。大大降低了成本。
下面定義一個(gè)客戶端來測試一下:
public class Test {
public static void main(String[] args) {
//選擇并創(chuàng)建需要使用的策略對(duì)象
Buyer strategy = new VipBuyer();
//創(chuàng)建上下文
Cashier cashier = new Cashier(strategy);
//計(jì)算價(jià)格
BigDecimal quote = cashier.quote(300);
System.out.println("普通會(huì)員商品的最終價(jià)格為:" + quote.doubleValue());
strategy = new SuperVipBuyer();
cashier = new Cashier(strategy);
quote = cashier.quote(300);
System.out.println("超級(jí)會(huì)員商品的最終價(jià)格為:" + quote.doubleValue());
}
}
輸出結(jié)果:
//普通會(huì)員商品的最終價(jià)格為:270.0 //超級(jí)會(huì)員商品的最終價(jià)格為:240.0
從上面的示例可以看出,策略模式僅僅封裝算法,提供新的算法插入到已有系統(tǒng)中,策略模式并不決定在何時(shí)使用何種算法。在什么情況下使用什么算法是由客戶端決定的。
策略模式的優(yōu)缺點(diǎn)
策略模式可以充分的體現(xiàn)面向?qū)ο笤O(shè)計(jì)原則中的封裝變化、多用組合,少用繼承、針對(duì)接口編程,不針對(duì)實(shí)現(xiàn)編程等原則。
策略模式具有以下特點(diǎn):
策略模式的關(guān)注點(diǎn)不是如何實(shí)現(xiàn)算法,而是如何組織、調(diào)用這些算法,從而讓程序結(jié)構(gòu)更靈活,具有更好的維護(hù)性和擴(kuò)展性。
策略模式中各個(gè)策略算法是平等的。對(duì)于一系列具體的策略算法,大家的地位是完全一樣的,正因?yàn)檫@個(gè)平等性,才能實(shí)現(xiàn)算法之間可以相互替換。所有的策略算法在實(shí)現(xiàn)上也是相互獨(dú)立的,相互之間是沒有依賴的。所以可以這樣描述這一系列策略算法:策略算法是相同行為的不同實(shí)現(xiàn)。
運(yùn)行期間,策略模式在每一個(gè)時(shí)刻只能使用一個(gè)具體的策略實(shí)現(xiàn)對(duì)象,雖然可以動(dòng)態(tài)地在不同的策略實(shí)現(xiàn)中切換,但是同時(shí)只能使用一個(gè)。
如果所有的具體策略類都有一些公有的行為。這時(shí)候,就應(yīng)當(dāng)把這些公有的行為放到共同的抽象策略角色Strategy類里面。當(dāng)然這時(shí)候抽象策略角色必須要用Java抽象類實(shí)現(xiàn),而不能使用接口。
但是,編程中沒有銀彈,策略模式也不例外,他也有一些缺點(diǎn),我們先來回顧總結(jié)下他的優(yōu)點(diǎn):
策略模式提供了對(duì)“開閉原則”的完美支持,用戶可以在不修改原有系統(tǒng)的基礎(chǔ)上選擇算法或行為,也可以靈活地增加新的算法或行為。
策略模式提供了管理相關(guān)的算法族的辦法。策略類的等級(jí)結(jié)構(gòu)定義了一個(gè)算法或行為族。恰當(dāng)使用繼承可以把公共的代碼移到父類里面,從而避免代碼重復(fù)。
使用策略模式可以避免使用多重條件(if-else)語句。多重條件語句不易維護(hù),它把采取哪一種算法或采取哪一種行為的邏輯與算法或行為的邏輯混合在一起,統(tǒng)統(tǒng)列在一個(gè)多重條件語句里面,比使用繼承的辦法還要原始和落后。
但同時(shí),他也有如下缺點(diǎn):
客戶端必須知道所有的策略類,并自行決定使用哪一個(gè)策略類。這就意味著客戶端必須理解這些算法的區(qū)別,以便適時(shí)選擇恰當(dāng)?shù)乃惴悺_@種策略類的創(chuàng)建及選擇其實(shí)也可以通過工廠模式來輔助進(jìn)行。
由于策略模式把每個(gè)具體的策略實(shí)現(xiàn)都多帶帶封裝成為類,如果備選的策略很多的話,那么對(duì)象的數(shù)目就會(huì)很可觀。可以通過使用享元模式在一定程度上減少對(duì)象的數(shù)量。
最后,附上本文內(nèi)容的思維導(dǎo)圖:
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/7389.html
摘要:軟件實(shí)現(xiàn)的是偽隨機(jī)數(shù)。有限狀態(tài)機(jī)不能產(chǎn)生真正的隨機(jī)數(shù)的。復(fù)聯(lián)中,滅霸打了指響之后,復(fù)仇者聯(lián)盟中存活和死亡的名單其實(shí)并不是隨機(jī)的。可見,滅霸的指響抹除過程并不是隨機(jī)的。綜上,滅霸的指響抹除過程不符合隨機(jī)性不可預(yù)測性以及不可復(fù)現(xiàn)性。showImg(https://user-gold-cdn.xitu.io/2019/5/7/16a91fc63239db4d);周末,陪女朋友去電影院看了《復(fù)仇者聯(lián)...
摘要:通俗一點(diǎn)說就是彩虹表犧牲了一點(diǎn)計(jì)算速度,換來的好處是較少的空間存儲(chǔ)更多的密碼數(shù)據(jù)。關(guān)于鹽的使用有一點(diǎn)需要說明加鹽的目的是為了增加提前構(gòu)造字典和彩虹表的代價(jià),并不是為了加密或增加密碼的計(jì)算復(fù)雜性。 背景 密碼是用來進(jìn)行鑒權(quán)(身份認(rèn)證)一種手段,說白了就是證明你是誰。一般鑒權(quán)都可以總結(jié)為下面3種形式: 你知道什么? (如密碼,密碼提示問題等) 你有什么? (如信用卡,token卡等) 你...
摘要:基因追本溯源在編程語言的歷史長河中,曾經(jīng)出現(xiàn)過很多編程語言。的歷史繼承年,網(wǎng)景公司招募了,目的是將編程語言嵌入到中。網(wǎng)景公司決定,他們想創(chuàng)建的腳本語言將補(bǔ)充,并且應(yīng)該有一個(gè)類似的語法,排除采用,,或等其他語言。 引子: 很多時(shí)候,當(dāng)我要字符串截取時(shí),我會(huì)想到substr和substring的方法,但是具體要怎么傳參數(shù)時(shí),我總是記不住。哪個(gè)應(yīng)該傳個(gè)字符串長度,哪個(gè)又應(yīng)該傳個(gè)開始和結(jié)尾的下...
摘要:基因追本溯源在編程語言的歷史長河中,曾經(jīng)出現(xiàn)過很多編程語言。的歷史繼承年,網(wǎng)景公司招募了,目的是將編程語言嵌入到中。網(wǎng)景公司決定,他們想創(chuàng)建的腳本語言將補(bǔ)充,并且應(yīng)該有一個(gè)類似的語法,排除采用,,或等其他語言。 引子: 很多時(shí)候,當(dāng)我要字符串截取時(shí),我會(huì)想到substr和substring的方法,但是具體要怎么傳參數(shù)時(shí),我總是記不住。哪個(gè)應(yīng)該傳個(gè)字符串長度,哪個(gè)又應(yīng)該傳個(gè)開始和結(jié)尾的下...
閱讀 3933·2021-09-22 10:02
閱讀 3365·2019-08-30 15:52
閱讀 3060·2019-08-30 12:51
閱讀 753·2019-08-30 11:08
閱讀 2064·2019-08-29 15:18
閱讀 3105·2019-08-29 12:13
閱讀 3591·2019-08-29 11:29
閱讀 1872·2019-08-29 11:13