Java設(shè)計(jì)模式之策略模式示例詳解
在講策略模式之前,我們先看一個(gè)日常生活中的小例子:
現(xiàn)實(shí)生活中我們到商場(chǎng)買東西的時(shí)候,賣場(chǎng)往往根據(jù)不同的客戶制定不同的報(bào)價(jià)策略,比如針對(duì)新客戶不打折扣,針對(duì)老客戶打9折,針對(duì)VIP客戶打8折...
現(xiàn)在我們要做一個(gè)報(bào)價(jià)管理的模塊,簡(jiǎn)要點(diǎn)就是要針對(duì)不同的客戶,提供不同的折扣報(bào)價(jià)。
如果是有你來(lái)做,你會(huì)怎么做?
我們很有可能寫出下面的代碼:
package strategy.examp02; import java.math.BigDecimal; public class QuoteManager { public BigDecimal quote(BigDecimal originalPrice,String customType){ if ("新客戶".equals(customType)) { System.out.println("抱歉!新客戶沒有折扣!"); return originalPrice; }else if ("老客戶".equals(customType)) { System.out.println("恭喜你!老客戶打9折!"); originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; }else if("VIP客戶".equals(customType)){ System.out.println("恭喜你!VIP客戶打8折!"); originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; } //其他人員都是原價(jià) return originalPrice; } }
經(jīng)過(guò)測(cè)試,上面的代碼工作的很好,可是上面的代碼是有問(wèn)題的。上面存在的問(wèn)題:把不同客戶的報(bào)價(jià)的算法都放在了同一個(gè)方法里面,使得該方法很是龐大(現(xiàn)在是只是一個(gè)演示,所以看起來(lái)還不是很臃腫)。
下面看一下上面的改進(jìn),我們把不同客戶的報(bào)價(jià)的算法都單獨(dú)作為一個(gè)方法
package strategy.examp02; import java.math.BigDecimal; public class QuoteManagerImprove { public BigDecimal quote(BigDecimal originalPrice, String customType){ if ("新客戶".equals(customType)) { return this.quoteNewCustomer(originalPrice); }else if ("老客戶".equals(customType)) { return this.quoteOldCustomer(originalPrice); }else if("VIP客戶".equals(customType)){ return this.quoteVIPCustomer(originalPrice); } //其他人員都是原價(jià) return originalPrice; } /** * 對(duì)VIP客戶的報(bào)價(jià)算法 * @param originalPrice 原價(jià) * @return 折后價(jià) */ private BigDecimal quoteVIPCustomer(BigDecimal originalPrice) { System.out.println("恭喜!VIP客戶打8折"); originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; } /** * 對(duì)老客戶的報(bào)價(jià)算法 * @param originalPrice 原價(jià) * @return 折后價(jià) */ private BigDecimal quoteOldCustomer(BigDecimal originalPrice) { System.out.println("恭喜!老客戶打9折"); originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; } /** * 對(duì)新客戶的報(bào)價(jià)算法 * @param originalPrice 原價(jià) * @return 折后價(jià) */ private BigDecimal quoteNewCustomer(BigDecimal originalPrice) { System.out.println("抱歉!新客戶沒有折扣!"); return originalPrice; } }
上面的代碼比剛開始的時(shí)候要好一點(diǎn),它把每個(gè)具體的算法都單獨(dú)抽出來(lái)作為一個(gè)方法,當(dāng)某一個(gè)具體的算法有了變動(dòng)的時(shí)候,只需要修改響應(yīng)的報(bào)價(jià)算法就可以了。
但是改進(jìn)后的代碼還是有問(wèn)題的,那有什么問(wèn)題呢?
1.當(dāng)我們新增一個(gè)客戶類型的時(shí)候,首先要添加一個(gè)該種客戶類型的報(bào)價(jià)算法方法,然后再quote方法中再加一個(gè)else if的分支,是不是感覺很是麻煩呢?而且這也違反了設(shè)計(jì)原則之一的開閉原則(open-closed-principle).
開閉原則:
對(duì)于擴(kuò)展是開放的(Open for extension)。這意味著模塊的行為是可以擴(kuò)展的。當(dāng)應(yīng)用的需求改變時(shí),我們可以對(duì)模塊進(jìn)行擴(kuò)展,使其具有滿足那些改變的新行為。也就是說(shuō),我們可以改變模塊的功能。
對(duì)于修改是關(guān)閉的(Closed for modification)。對(duì)模塊行為進(jìn)行擴(kuò)展時(shí),不必改動(dòng)模塊的源代碼或者二進(jìn)制代碼。
2.我們經(jīng)常會(huì)面臨這樣的情況,不同的時(shí)期使用不同的報(bào)價(jià)規(guī)則,比如在各個(gè)節(jié)假日舉行的各種促銷活動(dòng)時(shí)、商場(chǎng)店慶時(shí)往往都有普遍的折扣,但是促銷時(shí)間一旦過(guò)去,報(bào)價(jià)就要回到正常價(jià)格上來(lái)。按照上面的代碼我們就得修改if else里面的代碼很是麻煩
那有沒有什么辦法使得我們的報(bào)價(jià)管理即可擴(kuò)展、可維護(hù),又可以方便的響應(yīng)變化呢?當(dāng)然有解決方案啦,就是我們下面要講的策略模式。
定義
策略模式定義了一系列的算法,并將每一個(gè)算法封裝起來(lái),使每個(gè)算法可以相互替代,使算法本身和使用算法的客戶端分割開來(lái),相互獨(dú)立。
結(jié)構(gòu)
1.策略接口角色I(xiàn)Strategy:用來(lái)約束一系列具體的策略算法,策略上下文角色ConcreteStrategy使用此策略接口來(lái)調(diào)用具體的策略所實(shí)現(xiàn)的算法。
2.具體策略實(shí)現(xiàn)角色ConcreteStrategy:具體的策略實(shí)現(xiàn),即具體的算法實(shí)現(xiàn)。
3.策略上下文角色StrategyContext:策略上下文,負(fù)責(zé)和具體的策略實(shí)現(xiàn)交互,通常策略上下文對(duì)象會(huì)持有一個(gè)真正的策略實(shí)現(xiàn)對(duì)象,策略上下文還可以讓具體的策略實(shí)現(xiàn)從其中獲取相關(guān)數(shù)據(jù),回調(diào)策略上下文對(duì)象的方法。
UML類圖
UML序列圖
策略模式代碼的一般通用實(shí)現(xiàn):
策略接口
package strategy.examp01; //策略接口 public interface IStrategy { //定義的抽象算法方法 來(lái)約束具體的算法實(shí)現(xiàn)方法 public void algorithmMethod(); }
具體的策略實(shí)現(xiàn):
package strategy.examp01; // 具體的策略實(shí)現(xiàn) public class ConcreteStrategy implements IStrategy { //具體的算法實(shí)現(xiàn) @Override public void algorithmMethod() { System.out.println("this is ConcreteStrategy method..."); } }
package strategy.examp01; // 具體的策略實(shí)現(xiàn)2 public class ConcreteStrategy2 implements IStrategy { //具體的算法實(shí)現(xiàn) @Override public void algorithmMethod() { System.out.println("this is ConcreteStrategy2 method..."); } }
策略上下文:
package strategy.examp01; /** * 策略上下文 */ public class StrategyContext { //持有一個(gè)策略實(shí)現(xiàn)的引用 private IStrategy strategy; //使用構(gòu)造器注入具體的策略類 public StrategyContext(IStrategy strategy) { this.strategy = strategy; } public void contextMethod(){ //調(diào)用策略實(shí)現(xiàn)的方法 strategy.algorithmMethod(); } }
外部客戶端:
package strategy.examp01; //外部客戶端 public class Client { public static void main(String[] args) { //1.創(chuàng)建具體測(cè)策略實(shí)現(xiàn) IStrategy strategy = new ConcreteStrategy2(); //2.在創(chuàng)建策略上下文的同時(shí),將具體的策略實(shí)現(xiàn)對(duì)象注入到策略上下文當(dāng)中 StrategyContext ctx = new StrategyContext(strategy); //3.調(diào)用上下文對(duì)象的方法來(lái)完成對(duì)具體策略實(shí)現(xiàn)的回調(diào) ctx.contextMethod(); } }
針對(duì)我們一開始講的報(bào)價(jià)管理的例子:我們可以應(yīng)用策略模式對(duì)其進(jìn)行改造,不同類型的客戶有不同的折扣,我們可以將不同類型的客戶的報(bào)價(jià)規(guī)則都封裝為一個(gè)獨(dú)立的算法,然后抽象出這些報(bào)價(jià)算法的公共接口
公共報(bào)價(jià)策略接口:
package strategy.examp02; import java.math.BigDecimal; //報(bào)價(jià)策略接口 public interface IQuoteStrategy { //獲取折后價(jià)的價(jià)格 BigDecimal getPrice(BigDecimal originalPrice); }
新客戶報(bào)價(jià)策略實(shí)現(xiàn):
package strategy.examp02; import java.math.BigDecimal; //新客戶的報(bào)價(jià)策略實(shí)現(xiàn)類 public class NewCustomerQuoteStrategy implements IQuoteStrategy { @Override public BigDecimal getPrice(BigDecimal originalPrice) { System.out.println("抱歉!新客戶沒有折扣!"); return originalPrice; } }
老客戶報(bào)價(jià)策略實(shí)現(xiàn):
package strategy.examp02; import java.math.BigDecimal; //老客戶的報(bào)價(jià)策略實(shí)現(xiàn) public class OldCustomerQuoteStrategy implements IQuoteStrategy { @Override public BigDecimal getPrice(BigDecimal originalPrice) { System.out.println("恭喜!老客戶享有9折優(yōu)惠!"); originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; } }
VIP客戶報(bào)價(jià)策略實(shí)現(xiàn):
package strategy.examp02; import java.math.BigDecimal; //VIP客戶的報(bào)價(jià)策略實(shí)現(xiàn) public class VIPCustomerQuoteStrategy implements IQuoteStrategy { @Override public BigDecimal getPrice(BigDecimal originalPrice) { System.out.println("恭喜!VIP客戶享有8折優(yōu)惠!"); originalPrice = originalPrice.multiply(new BigDecimal(0.8)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; } }
報(bào)價(jià)上下文:
package strategy.examp02; import java.math.BigDecimal; //報(bào)價(jià)上下文角色 public class QuoteContext { //持有一個(gè)具體的報(bào)價(jià)策略 private IQuoteStrategy quoteStrategy; //注入報(bào)價(jià)策略 public QuoteContext(IQuoteStrategy quoteStrategy){ this.quoteStrategy = quoteStrategy; } //回調(diào)具體報(bào)價(jià)策略的方法 public BigDecimal getPrice(BigDecimal originalPrice){ return quoteStrategy.getPrice(originalPrice); } }
外部客戶端:
package strategy.examp02; import java.math.BigDecimal; //外部客戶端 public class Client { public static void main(String[] args) { //1.創(chuàng)建老客戶的報(bào)價(jià)策略 IQuoteStrategy oldQuoteStrategy = new OldCustomerQuoteStrategy(); //2.創(chuàng)建報(bào)價(jià)上下文對(duì)象,并設(shè)置具體的報(bào)價(jià)策略 QuoteContext quoteContext = new QuoteContext(oldQuoteStrategy); //3.調(diào)用報(bào)價(jià)上下文的方法 BigDecimal price = quoteContext.getPrice(new BigDecimal(100)); System.out.println("折扣價(jià)為:" +price); } }
控制臺(tái)輸出:
恭喜!老客戶享有9折優(yōu)惠!
折扣價(jià)為:90.00
這個(gè)時(shí)候,商場(chǎng)營(yíng)銷部新推出了一個(gè)客戶類型--MVP用戶(Most Valuable Person),可以享受折扣7折優(yōu)惠,那該怎么辦呢?
這個(gè)很容易,只要新增一個(gè)報(bào)價(jià)策略的實(shí)現(xiàn),然后外部客戶端調(diào)用的時(shí)候,創(chuàng)建這個(gè)新增的報(bào)價(jià)策略實(shí)現(xiàn),并設(shè)置到策略上下文就可以了,對(duì)原來(lái)已經(jīng)實(shí)現(xiàn)的代碼沒有任何的改動(dòng)。
MVP用戶的報(bào)價(jià)策略實(shí)現(xiàn):
package strategy.examp02; import java.math.BigDecimal; //MVP客戶的報(bào)價(jià)策略實(shí)現(xiàn) public class MVPCustomerQuoteStrategy implements IQuoteStrategy { @Override public BigDecimal getPrice(BigDecimal originalPrice) { System.out.println("哇偶!MVP客戶享受7折優(yōu)惠?。。?); originalPrice = originalPrice.multiply(new BigDecimal(0.7)).setScale(2,BigDecimal.ROUND_HALF_UP); return originalPrice; } }
外部客戶端:
package strategy.examp02; import java.math.BigDecimal; //外部客戶端 public class Client { public static void main(String[] args) { //創(chuàng)建MVP客戶的報(bào)價(jià)策略 IQuoteStrategy mvpQuoteStrategy = new MVPCustomerQuoteStrategy(); //創(chuàng)建報(bào)價(jià)上下文對(duì)象,并設(shè)置具體的報(bào)價(jià)策略 QuoteContext quoteContext = new QuoteContext(mvpQuoteStrategy); //調(diào)用報(bào)價(jià)上下文的方法 BigDecimal price = quoteContext.getPrice(new BigDecimal(100)); System.out.println("折扣價(jià)為:" +price); } }
控制臺(tái)輸出:
哇偶!MVP客戶享受7折優(yōu)惠?。?!
折扣價(jià)為:70.00
深入理解策略模式
策略模式的作用:就是把具體的算法實(shí)現(xiàn)從業(yè)務(wù)邏輯中剝離出來(lái),成為一系列獨(dú)立算法類,使得它們可以相互替換。
策略模式的著重點(diǎn):不是如何來(lái)實(shí)現(xiàn)算法,而是如何組織和調(diào)用這些算法,從而讓我們的程序結(jié)構(gòu)更加的靈活、可擴(kuò)展。
我們前面的第一個(gè)報(bào)價(jià)管理的示例,發(fā)現(xiàn)每個(gè)策略算法實(shí)現(xiàn)對(duì)應(yīng)的都是在QuoteManager 中quote方法中的if else語(yǔ)句里面,我們知道if else if語(yǔ)句里面的代碼在執(zhí)行的可能性方面可以說(shuō)是平等的,你要么執(zhí)行if,要么執(zhí)行else,要么執(zhí)行else if。
策略模式就是把各個(gè)平等的具體實(shí)現(xiàn)進(jìn)行抽象、封裝成為獨(dú)立的算法類,然后通過(guò)上下文和具體的算法類來(lái)進(jìn)行交互。各個(gè)策略算法都是平等的,地位是一樣的,正是由于各個(gè)算法的平等性,所以它們才是可以相互替換的。雖然我們可以動(dòng)態(tài)的切換各個(gè)策略,但是同一時(shí)刻只能使用一個(gè)策略。
在這個(gè)點(diǎn)上,我們舉個(gè)歷史上有名的故事作為示例:
三國(guó)劉備取西川時(shí),謀士龐統(tǒng)給的上、中、下三個(gè)計(jì)策:
上策:挑選精兵,晝夜兼行直接偷襲成都,可以一舉而定,此為上計(jì)計(jì)也。
中策:楊懷、高沛是蜀中名將,手下有精銳部隊(duì),而且據(jù)守關(guān)頭,我們可以裝作要回荊州,引他們輕騎來(lái)見,可就此將其擒殺,而后進(jìn)兵成都,此為中計(jì)。
下策:退還白帝,連引荊州,慢慢進(jìn)圖益州,此為下計(jì)。
這三個(gè)計(jì)策都是取西川的計(jì)策,也就是攻取西川這個(gè)問(wèn)題的具體的策略算法,劉備可以采用上策,可以采用中策,當(dāng)然也可以采用下策,由此可見策略模式的各種具體的策略算法都是平等的,可以相互替換。
那誰(shuí)來(lái)選擇具體采用哪種計(jì)策(算法)?
在這個(gè)故事中當(dāng)然是劉備選擇了,也就是外部的客戶端選擇使用某個(gè)具體的算法,然后把該算法(計(jì)策)設(shè)置到上下文當(dāng)中;
還有一種情況就是客戶端不選擇具體的算法,把這個(gè)事交給上下文,這相當(dāng)于劉備說(shuō)我不管有哪些攻取西川的計(jì)策,我只要結(jié)果(成功的拿下西川),具體怎么攻占(有哪些計(jì)策,怎么選擇)由參謀部來(lái)決定(上下文)。
下面我們演示下這種情景:
//攻取西川的策略 public interface IOccupationStrategyWestOfSiChuan { public void occupationWestOfSiChuan(String msg); }
//攻取西川的上上計(jì)策 public class UpperStrategy implements IOccupationStrategyWestOfSiChuan { @Override public void occupationWestOfSiChuan(String msg) { if (msg == null || msg.length() < 5) { //故意設(shè)置障礙,導(dǎo)致上上計(jì)策失敗 System.out.println("由于計(jì)劃泄露,上上計(jì)策失??!"); int i = 100/0; } System.out.println("挑選精兵,晝夜兼行直接偷襲成都,可以一舉而定,此為上計(jì)計(jì)也!"); } }
//攻取西川的中計(jì)策 public class MiddleStrategy implements IOccupationStrategyWestOfSiChuan { @Override public void occupationWestOfSiChuan(String msg) { System.out.println("楊懷、高沛是蜀中名將,手下有精銳部隊(duì),而且據(jù)守關(guān)頭,我們可以裝作要回荊州,引他們輕騎來(lái)見,可就此將其擒殺,而后進(jìn)兵成都,此為中計(jì)。"); } }
//攻取西川的下計(jì)策 public class LowerStrategy implements IOccupationStrategyWestOfSiChuan { @Override public void occupationWestOfSiChuan(String msg) { System.out.println("退還白帝,連引荊州,慢慢進(jìn)圖益州,此為下計(jì)。"); } }
//攻取西川參謀部,就是上下文啦,由上下文來(lái)選擇具體的策略 public class OccupationContext { public void occupationWestOfSichuan(String msg){ //先用上上計(jì)策 IOccupationStrategyWestOfSiChuan strategy = new UpperStrategy(); try { strategy.occupationWestOfSiChuan(msg); } catch (Exception e) { //上上計(jì)策有問(wèn)題行不通之后,用中計(jì)策 strategy = new MiddleStrategy(); strategy.occupationWestOfSiChuan(msg); } } }
//此時(shí)外部客戶端相當(dāng)于劉備了,不管具體采用什么計(jì)策,只要結(jié)果(成功的攻下西川) public class Client { public static void main(String[] args) { OccupationContext context = new OccupationContext(); //這個(gè)給手下的人激勵(lì)不夠啊 context.occupationWestOfSichuan("拿下西川"); System.out.println("========================="); //這個(gè)人人有賞,讓士兵有動(dòng)力啊 context.occupationWestOfSichuan("拿下西川之后,人人有賞!"); } }
控制臺(tái)輸出:
由于計(jì)劃泄露,上上計(jì)策失??!
楊懷、高沛是蜀中名將,手下有精銳部隊(duì),而且據(jù)守關(guān)頭,我們可以裝作要回荊州,引他們輕騎來(lái)見,可就此將其擒殺,而后進(jìn)兵成都,此為中計(jì)。
=========================
挑選精兵,晝夜兼行直接偷襲成都,可以一舉而定,此為上計(jì)計(jì)也!
我們上面的策略接口采用的是接口的形式來(lái)定義的,其實(shí)這個(gè)策略接口,是廣義上的接口,不是語(yǔ)言層面的interface,也可以是一個(gè)抽象類,如果多個(gè)算法具有公有的數(shù)據(jù),則可以將策略接口設(shè)計(jì)為一個(gè)抽象類,把公共的東西放到抽象類里面去。
策略和上下文的關(guān)系
在策略模式中,一般情況下都是上下文持有策略的引用,以進(jìn)行對(duì)具體策略的調(diào)用。但具體的策略對(duì)象也可以從上下文中獲取所需數(shù)據(jù),可以將上下文當(dāng)做參數(shù)傳入到具體策略中,具體策略通過(guò)回調(diào)上下文中的方法來(lái)獲取其所需要的數(shù)據(jù)。
下面我們演示這種情況:
在跨國(guó)公司中,一般都會(huì)在各個(gè)國(guó)家和地區(qū)設(shè)置分支機(jī)構(gòu),聘用當(dāng)?shù)厝藶閱T工,這樣就有這樣一個(gè)需要:每月發(fā)工資的時(shí)候,中國(guó)國(guó)籍的員工要發(fā)人民幣,美國(guó)國(guó)籍的員工要發(fā)美元,英國(guó)國(guó)籍的要發(fā)英鎊。
//支付策略接口 public interface PayStrategy { //在支付策略接口的支付方法中含有支付上下文作為參數(shù),以便在具體的支付策略中回調(diào)上下文中的方法獲取數(shù)據(jù) public void pay(PayContext ctx); }
//人民幣支付策略 public class RMBPay implements PayStrategy { @Override public void pay(PayContext ctx) { System.out.println("現(xiàn)在給:"+ctx.getUsername()+" 人民幣支付 "+ctx.getMoney()+"元!"); } }
//美金支付策略 public class DollarPay implements PayStrategy { @Override public void pay(PayContext ctx) { System.out.println("現(xiàn)在給:"+ctx.getUsername()+" 美金支付 "+ctx.getMoney()+"dollar !"); } }
//支付上下文,含有多個(gè)算法的公有數(shù)據(jù) public class PayContext { //員工姓名 private String username; //員工的工資 private double money; //支付策略 private PayStrategy payStrategy; public void pay(){ //調(diào)用具體的支付策略來(lái)進(jìn)行支付 payStrategy.pay(this); } public PayContext(String username, double money, PayStrategy payStrategy) { this.username = username; this.money = money; this.payStrategy = payStrategy; } public String getUsername() { return username; } public double getMoney() { return money; } }
//外部客戶端 public class Client { public static void main(String[] args) { //創(chuàng)建具體的支付策略 PayStrategy rmbStrategy = new RMBPay(); PayStrategy dollarStrategy = new DollarPay(); //準(zhǔn)備小王的支付上下文 PayContext ctx = new PayContext("小王",30000,rmbStrategy); //向小王支付工資 ctx.pay(); //準(zhǔn)備Jack的支付上下文 ctx = new PayContext("jack",10000,dollarStrategy); //向Jack支付工資 ctx.pay(); } }
控制臺(tái)輸出:
現(xiàn)在給:小王 人民幣支付 30000.0元!
現(xiàn)在給:jack 美金支付 10000.0dollar !
那現(xiàn)在我們要新增一個(gè)銀行賬戶的支付策略,該怎么辦呢?
顯然我們應(yīng)該新增一個(gè)支付找銀行賬戶的策略實(shí)現(xiàn),由于需要從上下文中獲取數(shù)據(jù),為了不修改已有的上下文,我們可以通過(guò)繼承已有的上下文來(lái)擴(kuò)展一個(gè)新的帶有銀行賬戶的上下文,然后再客戶端中使用新的策略實(shí)現(xiàn)和帶有銀行賬戶的上下文,這樣之前已有的實(shí)現(xiàn)完全不需要改動(dòng),遵守了開閉原則。
//銀行賬戶支付 public class AccountPay implements PayStrategy { @Override public void pay(PayContext ctx) { PayContextWithAccount ctxAccount = (PayContextWithAccount) ctx; System.out.println("現(xiàn)在給:"+ctxAccount.getUsername()+"的賬戶:"+ctxAccount.getAccount()+" 支付工資:"+ctxAccount.getMoney()+" 元!"); } }
//帶銀行賬戶的支付上下文 public class PayContextWithAccount extends PayContext { //銀行賬戶 private String account; public PayContextWithAccount(String username, double money, PayStrategy payStrategy,String account) { super(username, money, payStrategy); this.account = account; } public String getAccount() { return account; } }
//外部客戶端 public class Client { public static void main(String[] args) { //創(chuàng)建具體的支付策略 PayStrategy rmbStrategy = new RMBPay(); PayStrategy dollarStrategy = new DollarPay(); //準(zhǔn)備小王的支付上下文 PayContext ctx = new PayContext("小王",30000,rmbStrategy); //向小王支付工資 ctx.pay(); //準(zhǔn)備Jack的支付上下文 ctx = new PayContext("jack",10000,dollarStrategy); //向Jack支付工資 ctx.pay(); //創(chuàng)建支付到銀行賬戶的支付策略 PayStrategy accountStrategy = new AccountPay(); //準(zhǔn)備帶有銀行賬戶的上下文 ctx = new PayContextWithAccount("小張",40000,accountStrategy,"1234567890"); //向小張的賬戶支付 ctx.pay(); } }
控制臺(tái)輸出:
現(xiàn)在給:小王 人民幣支付 30000.0元!
現(xiàn)在給:jack 美金支付 10000.0dollar !
現(xiàn)在給:小張的賬戶:1234567890 支付工資:40000.0 元!
除了上面的方法,還有其他的實(shí)現(xiàn)方式嗎?
當(dāng)然有了,上面的實(shí)現(xiàn)方式是策略實(shí)現(xiàn)所需要的數(shù)據(jù)都是從上下文中獲取,因此擴(kuò)展了上下文;現(xiàn)在我們可以不擴(kuò)展上下文,直接從策略實(shí)現(xiàn)內(nèi)部來(lái)獲取數(shù)據(jù),看下面的實(shí)現(xiàn):
//支付到銀行賬戶的策略 public class AccountPay2 implements PayStrategy { //銀行賬戶 private String account; public AccountPay2(String account) { this.account = account; } @Override public void pay(PayContext ctx) { System.out.println("現(xiàn)在給:"+ctx.getUsername()+"的賬戶:"+getAccount()+" 支付工資:"+ctx.getMoney()+" 元!"); } public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } }
//外部客戶端 public class Client { public static void main(String[] args) { //創(chuàng)建具體的支付策略 PayStrategy rmbStrategy = new RMBPay(); PayStrategy dollarStrategy = new DollarPay(); //準(zhǔn)備小王的支付上下文 PayContext ctx = new PayContext("小王",30000,rmbStrategy); //向小王支付工資 ctx.pay(); //準(zhǔn)備Jack的支付上下文 ctx = new PayContext("jack",10000,dollarStrategy); //向Jack支付工資 ctx.pay(); //創(chuàng)建支付到銀行賬戶的支付策略 PayStrategy accountStrategy = new AccountPay2("1234567890"); //準(zhǔn)備上下文 ctx = new PayContext("小張",40000,accountStrategy); //向小張的賬戶支付 ctx.pay(); } }
控制臺(tái)輸出:
現(xiàn)在給:小王 人民幣支付 30000.0元!
現(xiàn)在給:jack 美金支付 10000.0dollar !
現(xiàn)在給:小張的賬戶:1234567890 支付工資:40000.0 元!
那我們來(lái)比較一下上面兩種實(shí)現(xiàn)方式:
擴(kuò)展上下文的實(shí)現(xiàn):
- 優(yōu)點(diǎn):具體的策略實(shí)現(xiàn)風(fēng)格很是統(tǒng)一,策略實(shí)現(xiàn)所需要的數(shù)據(jù)都是從上下文中獲取的,在上下文中添加的數(shù)據(jù),可以視為公共的數(shù)據(jù),其他的策略實(shí)現(xiàn)也可以使用。
- 缺點(diǎn):很明顯如果某些數(shù)據(jù)只是特定的策略實(shí)現(xiàn)需要,大部分的策略實(shí)現(xiàn)不需要,那這些數(shù)據(jù)有“浪費(fèi)”之嫌,另外如果每次添加算法數(shù)據(jù)都擴(kuò)展上下文,很容易導(dǎo)致上下文的層級(jí)很是復(fù)雜。
在具體的策略實(shí)現(xiàn)上添加所需要的數(shù)據(jù)的實(shí)現(xiàn):
- 優(yōu)點(diǎn):容易想到,實(shí)現(xiàn)簡(jiǎn)單
- 缺點(diǎn):與其他的策略實(shí)現(xiàn)風(fēng)格不一致,其他的策略實(shí)現(xiàn)所需數(shù)據(jù)都是來(lái)自上下文,而這個(gè)策略實(shí)現(xiàn)一部分?jǐn)?shù)據(jù)來(lái)自于自身,一部分?jǐn)?shù)據(jù)來(lái)自于上下文;外部在使用這個(gè)策略實(shí)現(xiàn)的時(shí)候也和其他的策略實(shí)現(xiàn)不一致了,難以以一個(gè)統(tǒng)一的方式動(dòng)態(tài)的切換策略實(shí)現(xiàn)。
策略模式在JDK中的應(yīng)用
在多線程編程中,我們經(jīng)常使用線程池來(lái)管理線程,以減緩線程頻繁的創(chuàng)建和銷毀帶來(lái)的資源的浪費(fèi),在創(chuàng)建線程池的時(shí)候,經(jīng)常使用一個(gè)工廠類來(lái)創(chuàng)建線程池Executors,實(shí)際上Executors的內(nèi)部使用的是類ThreadPoolExecutor.它有一個(gè)最終的構(gòu)造函數(shù)如下:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
- corePoolSize:線程池中的核心線程數(shù)量,即使這些線程沒有任務(wù)干,也不會(huì)將其銷毀。
- maximumPoolSize:線程池中的最多能夠創(chuàng)建的線程數(shù)量。
- keepAliveTime:當(dāng)線程池中的線程數(shù)量大于corePoolSize時(shí),多余的線程等待新任務(wù)的最長(zhǎng)時(shí)間。
- unit:keepAliveTime的時(shí)間單位。
- workQueue:在線程池中的線程還沒有還得及執(zhí)行任務(wù)之前,保存任務(wù)的隊(duì)列(當(dāng)線程池中的線程都有任務(wù)在執(zhí)行的時(shí)候,仍然有任務(wù)不斷的提交過(guò)來(lái),這些任務(wù)保存在workQueue隊(duì)列中)。
- threadFactory:創(chuàng)建線程池中線程的工廠。
- handler:當(dāng)線程池中沒有多余的線程來(lái)執(zhí)行任務(wù),并且保存任務(wù)的多列也滿了(指的是有界隊(duì)列),對(duì)仍在提交給線程池的任務(wù)的處理策略。
RejectedExecutionHandler 是一個(gè)策略接口,用在當(dāng)線程池中沒有多余的線程來(lái)執(zhí)行任務(wù),并且保存任務(wù)的多列也滿了(指的是有界隊(duì)列),對(duì)仍在提交給線程池的任務(wù)的處理策略。
線程池的具體介紹和實(shí)戰(zhàn),可以關(guān)注下公眾號(hào)Java技術(shù)棧,在后臺(tái)回復(fù):多線程,都是干貨。
public interface RejectedExecutionHandler { /** *當(dāng)ThreadPoolExecutor的execut方法調(diào)用時(shí),并且ThreadPoolExecutor不能接受一個(gè)任務(wù)Task時(shí),該方法就有可能被調(diào)用。 * 不能接受一個(gè)任務(wù)Task的原因:有可能是沒有多余的線程來(lái)處理,有可能是workqueue隊(duì)列中沒有多余的位置來(lái)存放該任務(wù),該方法有可能拋出一個(gè)未受檢的異常RejectedExecutionException * @param r the runnable task requested to be executed * @param executor the executor attempting to execute this task * @throws RejectedExecutionException if there is no remedy */ void rejectedExecution(Runnable r, ThreadPoolExecutor executor); }
該策略接口有四個(gè)實(shí)現(xiàn)類
AbortPolicy:該策略是直接將提交的任務(wù)拋棄掉,并拋出RejectedExecutionException異常。
/** * A handler for rejected tasks that throws a * <tt>RejectedExecutionException</tt>. */ public static class AbortPolicy implements RejectedExecutionHandler { /** * Creates an <tt>AbortPolicy</tt>. */ public AbortPolicy() { } /** * Always throws RejectedExecutionException. * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task * @throws RejectedExecutionException always. */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException(); } }
DiscardPolicy:該策略也是將任務(wù)拋棄掉(對(duì)于提交的任務(wù)不管不問(wèn),什么也不做),不過(guò)并不拋出異常。
/** * A handler for rejected tasks that silently discards the * rejected task. */ public static class DiscardPolicy implements RejectedExecutionHandler { /** * Creates a <tt>DiscardPolicy</tt>. */ public DiscardPolicy() { } /** * Does nothing, which has the effect of discarding task r. * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } }
DiscardOldestPolicy:該策略是當(dāng)執(zhí)行器未關(guān)閉時(shí),從任務(wù)隊(duì)列workQueue中取出第一個(gè)任務(wù),并拋棄這第一個(gè)任務(wù),進(jìn)而有空間存儲(chǔ)剛剛提交的任務(wù)。使用該策略要特別小心,因?yàn)樗鼤?huì)直接拋棄之前的任務(wù)。
/** * A handler for rejected tasks that discards the oldest unhandled * request and then retries <tt>execute</tt>, unless the executor * is shut down, in which case the task is discarded. */ public static class DiscardOldestPolicy implements RejectedExecutionHandler { /** * Creates a <tt>DiscardOldestPolicy</tt> for the given executor. */ public DiscardOldestPolicy() { } /** * Obtains and ignores the next task that the executor * would otherwise execute, if one is immediately available, * and then retries execution of task r, unless the executor * is shut down, in which case task r is instead discarded. * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { e.getQueue().poll(); e.execute(r); } } }
CallerRunsPolicy:該策略并沒有拋棄任何的任務(wù),由于線程池中已經(jīng)沒有了多余的線程來(lái)分配該任務(wù),該策略是在當(dāng)前線程(調(diào)用者線程)中直接執(zhí)行該任務(wù)。
/** * A handler for rejected tasks that runs the rejected task * directly in the calling thread of the {@code execute} method, * unless the executor has been shut down, in which case the task * is discarded. */ public static class CallerRunsPolicy implements RejectedExecutionHandler { /** * Creates a {@code CallerRunsPolicy}. */ public CallerRunsPolicy() { } /** * Executes task r in the caller's thread, unless the executor * has been shut down, in which case the task is discarded. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { r.run(); } } }
類ThreadPoolExecutor中持有一個(gè)RejectedExecutionHandler接口的引用,以便在構(gòu)造函數(shù)中可以由外部客戶端自己制定具體的策略并注入。下面看一下其類圖:
策略模式的優(yōu)點(diǎn)
策略模式的功能就是通過(guò)抽象、封裝來(lái)定義一系列的算法,使得這些算法可以相互替換,所以為這些算法定義一個(gè)公共的接口,以約束這些算法的功能實(shí)現(xiàn)。如果這些算法具有公共的功能,可以將接口變?yōu)槌橄箢?,將公共功能放到抽象父類里面?/p>
策略模式的一系列算法是可以相互替換的、是平等的,寫在一起就是if-else組織結(jié)構(gòu),如果算法實(shí)現(xiàn)里又有條件語(yǔ)句,就構(gòu)成了多重條件語(yǔ)句,可以用策略模式,避免這樣的多重條件語(yǔ)句。
擴(kuò)展性更好:在策略模式中擴(kuò)展策略實(shí)現(xiàn)非常的容易,只要新增一個(gè)策略實(shí)現(xiàn)類,然后在使用策略實(shí)現(xiàn)的地方,使用這個(gè)新的策略實(shí)現(xiàn)就好了。
策略模式的缺點(diǎn)
1.客戶端必須了解所有的策略,清楚它們的不同:
如果由客戶端來(lái)決定使用何種算法,那客戶端必須知道所有的策略,清楚各個(gè)策略的功能和不同,這樣才能做出正確的選擇,但是這暴露了策略的具體實(shí)現(xiàn)。
2.增加了對(duì)象的數(shù)量:
由于策略模式將每個(gè)具體的算法都單獨(dú)封裝為一個(gè)策略類,如果可選的策略有很多的話,那對(duì)象的數(shù)量也會(huì)很多。
3.只適合偏平的算法結(jié)構(gòu):
由于策略模式的各個(gè)策略實(shí)現(xiàn)是平等的關(guān)系(可相互替換),實(shí)際上就構(gòu)成了一個(gè)扁平的算法結(jié)構(gòu)。即一個(gè)策略接口下面有多個(gè)平等的策略實(shí)現(xiàn)(多個(gè)策略實(shí)現(xiàn)是兄弟關(guān)系),并且運(yùn)行時(shí)只能有一個(gè)算法被使用。這就限制了算法的使用層級(jí),且不能被嵌套。
策略模式的本質(zhì)
分離算法,選擇實(shí)現(xiàn)。
如果你仔細(xì)思考策略模式的結(jié)構(gòu)和功能的話,就會(huì)發(fā)現(xiàn):如果沒有上下文,策略模式就回到了最基本的接口和實(shí)現(xiàn)了,只要是面向接口編程,就能夠享受到面向接口編程帶來(lái)的好處,通過(guò)一個(gè)統(tǒng)一的策略接口來(lái)封裝和分離各個(gè)具體的策略實(shí)現(xiàn),無(wú)需關(guān)系具體的策略實(shí)現(xiàn)。
貌似沒有上下文什么事,但是如果沒有上下文的話,客戶端就必須直接和具體的策略實(shí)現(xiàn)進(jìn)行交互了,尤其是需要提供一些公共功能或者是存儲(chǔ)一些狀態(tài)的時(shí)候,會(huì)大大增加客戶端使用的難度;引入上下文之后,這部分工作可以由上下文來(lái)完成,客戶端只需要和上下文進(jìn)行交互就可以了。這樣可以讓策略模式更具有整體性,客戶端也更加的簡(jiǎn)單。
策略模式體現(xiàn)了開閉原則:策略模式把一系列的可變算法進(jìn)行封裝,從而定義了良好的程序結(jié)構(gòu),在出現(xiàn)新的算法的時(shí)候,可以很容易的將新的算法實(shí)現(xiàn)加入到已有的系統(tǒng)中,而已有的實(shí)現(xiàn)不需要修改。
策略模式體現(xiàn)了里氏替換原則:策略模式是一個(gè)扁平的結(jié)構(gòu),各個(gè)策略實(shí)現(xiàn)都是兄弟關(guān)系,實(shí)現(xiàn)了同一個(gè)接口或者繼承了同一個(gè)抽象類。這樣只要使用策略的客戶端保持面向抽象編程,就可以動(dòng)態(tài)的切換不同的策略實(shí)現(xiàn)以進(jìn)行替換。
以上就是Java設(shè)計(jì)模式之策略模式示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Java策略模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
ZooKeeper入門教程二在單機(jī)和集群環(huán)境下的安裝搭建及使用
本文是ZooKeeper入門系列教程,涵蓋ZooKeeper的安裝使及單機(jī)集群環(huán)境搭建,通過(guò)實(shí)例和大量圖表,結(jié)合實(shí)戰(zhàn),幫助學(xué)習(xí)者理解和運(yùn)用,有需要的朋友可以借鑒參考下2022-01-01SpringBoot整合log4j日志與HashMap的底層原理解析
這篇文章主要介紹了SpringBoot整合log4j日志與HashMap的底層原理,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01關(guān)于mybatis callSettersOnNulls 配置解析
這篇文章主要介紹了關(guān)于mybatis callSettersOnNulls 配置,非常不錯(cuò),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下2018-06-06Java中mkdir()和mkdirs()的區(qū)別及說(shuō)明
這篇文章主要介紹了Java中mkdir()和mkdirs()的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11解決Map集合使用get方法返回null拋出空指針異常問(wèn)題
這篇文章主要介紹了解決Map集合使用get方法返回null拋出空指針異常問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Spring擴(kuò)展BeanFactoryPostProcessor使用技巧詳解
這篇文章主要為大家介紹了Spring擴(kuò)展BeanFactoryPostProcessor使用技巧詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Java中5種方式實(shí)現(xiàn)String反轉(zhuǎn)
下面小編就為大家?guī)?lái)一篇Java中5種方式實(shí)現(xiàn)String反轉(zhuǎn)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。2016-06-06