Java設(shè)計模式之策略模式示例詳解
在講策略模式之前,我們先看一個日常生活中的小例子:
現(xiàn)實生活中我們到商場買東西的時候,賣場往往根據(jù)不同的客戶制定不同的報價策略,比如針對新客戶不打折扣,針對老客戶打9折,針對VIP客戶打8折...
現(xiàn)在我們要做一個報價管理的模塊,簡要點就是要針對不同的客戶,提供不同的折扣報價。
如果是有你來做,你會怎么做?
我們很有可能寫出下面的代碼:
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;
}
//其他人員都是原價
return originalPrice;
}
}
經(jīng)過測試,上面的代碼工作的很好,可是上面的代碼是有問題的。上面存在的問題:把不同客戶的報價的算法都放在了同一個方法里面,使得該方法很是龐大(現(xiàn)在是只是一個演示,所以看起來還不是很臃腫)。
下面看一下上面的改進,我們把不同客戶的報價的算法都單獨作為一個方法
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);
}
//其他人員都是原價
return originalPrice;
}
/**
* 對VIP客戶的報價算法
* @param originalPrice 原價
* @return 折后價
*/
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;
}
/**
* 對老客戶的報價算法
* @param originalPrice 原價
* @return 折后價
*/
private BigDecimal quoteOldCustomer(BigDecimal originalPrice) {
System.out.println("恭喜!老客戶打9折");
originalPrice = originalPrice.multiply(new BigDecimal(0.9)).setScale(2,BigDecimal.ROUND_HALF_UP);
return originalPrice;
}
/**
* 對新客戶的報價算法
* @param originalPrice 原價
* @return 折后價
*/
private BigDecimal quoteNewCustomer(BigDecimal originalPrice) {
System.out.println("抱歉!新客戶沒有折扣!");
return originalPrice;
}
}
上面的代碼比剛開始的時候要好一點,它把每個具體的算法都單獨抽出來作為一個方法,當(dāng)某一個具體的算法有了變動的時候,只需要修改響應(yīng)的報價算法就可以了。
但是改進后的代碼還是有問題的,那有什么問題呢?
1.當(dāng)我們新增一個客戶類型的時候,首先要添加一個該種客戶類型的報價算法方法,然后再quote方法中再加一個else if的分支,是不是感覺很是麻煩呢?而且這也違反了設(shè)計原則之一的開閉原則(open-closed-principle).
開閉原則:
對于擴展是開放的(Open for extension)。這意味著模塊的行為是可以擴展的。當(dāng)應(yīng)用的需求改變時,我們可以對模塊進行擴展,使其具有滿足那些改變的新行為。也就是說,我們可以改變模塊的功能。
對于修改是關(guān)閉的(Closed for modification)。對模塊行為進行擴展時,不必改動模塊的源代碼或者二進制代碼。
2.我們經(jīng)常會面臨這樣的情況,不同的時期使用不同的報價規(guī)則,比如在各個節(jié)假日舉行的各種促銷活動時、商場店慶時往往都有普遍的折扣,但是促銷時間一旦過去,報價就要回到正常價格上來。按照上面的代碼我們就得修改if else里面的代碼很是麻煩
那有沒有什么辦法使得我們的報價管理即可擴展、可維護,又可以方便的響應(yīng)變化呢?當(dāng)然有解決方案啦,就是我們下面要講的策略模式。
定義
策略模式定義了一系列的算法,并將每一個算法封裝起來,使每個算法可以相互替代,使算法本身和使用算法的客戶端分割開來,相互獨立。
結(jié)構(gòu)
1.策略接口角色IStrategy:用來約束一系列具體的策略算法,策略上下文角色ConcreteStrategy使用此策略接口來調(diào)用具體的策略所實現(xiàn)的算法。
2.具體策略實現(xiàn)角色ConcreteStrategy:具體的策略實現(xiàn),即具體的算法實現(xiàn)。
3.策略上下文角色StrategyContext:策略上下文,負(fù)責(zé)和具體的策略實現(xiàn)交互,通常策略上下文對象會持有一個真正的策略實現(xiàn)對象,策略上下文還可以讓具體的策略實現(xiàn)從其中獲取相關(guān)數(shù)據(jù),回調(diào)策略上下文對象的方法。
UML類圖

UML序列圖

策略模式代碼的一般通用實現(xiàn):
策略接口
package strategy.examp01;
//策略接口
public interface IStrategy {
//定義的抽象算法方法 來約束具體的算法實現(xiàn)方法
public void algorithmMethod();
}
具體的策略實現(xiàn):
package strategy.examp01;
// 具體的策略實現(xiàn)
public class ConcreteStrategy implements IStrategy {
//具體的算法實現(xiàn)
@Override
public void algorithmMethod() {
System.out.println("this is ConcreteStrategy method...");
}
}package strategy.examp01;
// 具體的策略實現(xiàn)2
public class ConcreteStrategy2 implements IStrategy {
//具體的算法實現(xiàn)
@Override
public void algorithmMethod() {
System.out.println("this is ConcreteStrategy2 method...");
}
}
策略上下文:
package strategy.examp01;
/**
* 策略上下文
*/
public class StrategyContext {
//持有一個策略實現(xiàn)的引用
private IStrategy strategy;
//使用構(gòu)造器注入具體的策略類
public StrategyContext(IStrategy strategy) {
this.strategy = strategy;
}
public void contextMethod(){
//調(diào)用策略實現(xiàn)的方法
strategy.algorithmMethod();
}
}
外部客戶端:
package strategy.examp01;
//外部客戶端
public class Client {
public static void main(String[] args) {
//1.創(chuàng)建具體測策略實現(xiàn)
IStrategy strategy = new ConcreteStrategy2();
//2.在創(chuàng)建策略上下文的同時,將具體的策略實現(xiàn)對象注入到策略上下文當(dāng)中
StrategyContext ctx = new StrategyContext(strategy);
//3.調(diào)用上下文對象的方法來完成對具體策略實現(xiàn)的回調(diào)
ctx.contextMethod();
}
}
針對我們一開始講的報價管理的例子:我們可以應(yīng)用策略模式對其進行改造,不同類型的客戶有不同的折扣,我們可以將不同類型的客戶的報價規(guī)則都封裝為一個獨立的算法,然后抽象出這些報價算法的公共接口
公共報價策略接口:
package strategy.examp02;
import java.math.BigDecimal;
//報價策略接口
public interface IQuoteStrategy {
//獲取折后價的價格
BigDecimal getPrice(BigDecimal originalPrice);
}
新客戶報價策略實現(xiàn):
package strategy.examp02;
import java.math.BigDecimal;
//新客戶的報價策略實現(xiàn)類
public class NewCustomerQuoteStrategy implements IQuoteStrategy {
@Override
public BigDecimal getPrice(BigDecimal originalPrice) {
System.out.println("抱歉!新客戶沒有折扣!");
return originalPrice;
}
}
老客戶報價策略實現(xiàn):
package strategy.examp02;
import java.math.BigDecimal;
//老客戶的報價策略實現(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客戶報價策略實現(xiàn):
package strategy.examp02;
import java.math.BigDecimal;
//VIP客戶的報價策略實現(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;
}
}
報價上下文:
package strategy.examp02;
import java.math.BigDecimal;
//報價上下文角色
public class QuoteContext {
//持有一個具體的報價策略
private IQuoteStrategy quoteStrategy;
//注入報價策略
public QuoteContext(IQuoteStrategy quoteStrategy){
this.quoteStrategy = quoteStrategy;
}
//回調(diào)具體報價策略的方法
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)建老客戶的報價策略
IQuoteStrategy oldQuoteStrategy = new OldCustomerQuoteStrategy();
//2.創(chuàng)建報價上下文對象,并設(shè)置具體的報價策略
QuoteContext quoteContext = new QuoteContext(oldQuoteStrategy);
//3.調(diào)用報價上下文的方法
BigDecimal price = quoteContext.getPrice(new BigDecimal(100));
System.out.println("折扣價為:" +price);
}
}
控制臺輸出:
恭喜!老客戶享有9折優(yōu)惠!
折扣價為:90.00
這個時候,商場營銷部新推出了一個客戶類型--MVP用戶(Most Valuable Person),可以享受折扣7折優(yōu)惠,那該怎么辦呢?
這個很容易,只要新增一個報價策略的實現(xiàn),然后外部客戶端調(diào)用的時候,創(chuàng)建這個新增的報價策略實現(xiàn),并設(shè)置到策略上下文就可以了,對原來已經(jīng)實現(xiàn)的代碼沒有任何的改動。
MVP用戶的報價策略實現(xiàn):
package strategy.examp02;
import java.math.BigDecimal;
//MVP客戶的報價策略實現(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客戶的報價策略
IQuoteStrategy mvpQuoteStrategy = new MVPCustomerQuoteStrategy();
//創(chuàng)建報價上下文對象,并設(shè)置具體的報價策略
QuoteContext quoteContext = new QuoteContext(mvpQuoteStrategy);
//調(diào)用報價上下文的方法
BigDecimal price = quoteContext.getPrice(new BigDecimal(100));
System.out.println("折扣價為:" +price);
}
}
控制臺輸出:
哇偶!MVP客戶享受7折優(yōu)惠?。。?br />折扣價為:70.00
深入理解策略模式
策略模式的作用:就是把具體的算法實現(xiàn)從業(yè)務(wù)邏輯中剝離出來,成為一系列獨立算法類,使得它們可以相互替換。
策略模式的著重點:不是如何來實現(xiàn)算法,而是如何組織和調(diào)用這些算法,從而讓我們的程序結(jié)構(gòu)更加的靈活、可擴展。
我們前面的第一個報價管理的示例,發(fā)現(xiàn)每個策略算法實現(xiàn)對應(yīng)的都是在QuoteManager 中quote方法中的if else語句里面,我們知道if else if語句里面的代碼在執(zhí)行的可能性方面可以說是平等的,你要么執(zhí)行if,要么執(zhí)行else,要么執(zhí)行else if。
策略模式就是把各個平等的具體實現(xiàn)進行抽象、封裝成為獨立的算法類,然后通過上下文和具體的算法類來進行交互。各個策略算法都是平等的,地位是一樣的,正是由于各個算法的平等性,所以它們才是可以相互替換的。雖然我們可以動態(tài)的切換各個策略,但是同一時刻只能使用一個策略。
在這個點上,我們舉個歷史上有名的故事作為示例:
三國劉備取西川時,謀士龐統(tǒng)給的上、中、下三個計策:
上策:挑選精兵,晝夜兼行直接偷襲成都,可以一舉而定,此為上計計也。
中策:楊懷、高沛是蜀中名將,手下有精銳部隊,而且據(jù)守關(guān)頭,我們可以裝作要回荊州,引他們輕騎來見,可就此將其擒殺,而后進兵成都,此為中計。
下策:退還白帝,連引荊州,慢慢進圖益州,此為下計。
這三個計策都是取西川的計策,也就是攻取西川這個問題的具體的策略算法,劉備可以采用上策,可以采用中策,當(dāng)然也可以采用下策,由此可見策略模式的各種具體的策略算法都是平等的,可以相互替換。
那誰來選擇具體采用哪種計策(算法)?
在這個故事中當(dāng)然是劉備選擇了,也就是外部的客戶端選擇使用某個具體的算法,然后把該算法(計策)設(shè)置到上下文當(dāng)中;
還有一種情況就是客戶端不選擇具體的算法,把這個事交給上下文,這相當(dāng)于劉備說我不管有哪些攻取西川的計策,我只要結(jié)果(成功的拿下西川),具體怎么攻占(有哪些計策,怎么選擇)由參謀部來決定(上下文)。
下面我們演示下這種情景:
//攻取西川的策略
public interface IOccupationStrategyWestOfSiChuan {
public void occupationWestOfSiChuan(String msg);
}
//攻取西川的上上計策
public class UpperStrategy implements IOccupationStrategyWestOfSiChuan {
@Override
public void occupationWestOfSiChuan(String msg) {
if (msg == null || msg.length() < 5) {
//故意設(shè)置障礙,導(dǎo)致上上計策失敗
System.out.println("由于計劃泄露,上上計策失敗!");
int i = 100/0;
}
System.out.println("挑選精兵,晝夜兼行直接偷襲成都,可以一舉而定,此為上計計也!");
}
}
//攻取西川的中計策
public class MiddleStrategy implements IOccupationStrategyWestOfSiChuan {
@Override
public void occupationWestOfSiChuan(String msg) {
System.out.println("楊懷、高沛是蜀中名將,手下有精銳部隊,而且據(jù)守關(guān)頭,我們可以裝作要回荊州,引他們輕騎來見,可就此將其擒殺,而后進兵成都,此為中計。");
}
}
//攻取西川的下計策
public class LowerStrategy implements IOccupationStrategyWestOfSiChuan {
@Override
public void occupationWestOfSiChuan(String msg) {
System.out.println("退還白帝,連引荊州,慢慢進圖益州,此為下計。");
}
}
//攻取西川參謀部,就是上下文啦,由上下文來選擇具體的策略
public class OccupationContext {
public void occupationWestOfSichuan(String msg){
//先用上上計策
IOccupationStrategyWestOfSiChuan strategy = new UpperStrategy();
try {
strategy.occupationWestOfSiChuan(msg);
} catch (Exception e) {
//上上計策有問題行不通之后,用中計策
strategy = new MiddleStrategy();
strategy.occupationWestOfSiChuan(msg);
}
}
}
//此時外部客戶端相當(dāng)于劉備了,不管具體采用什么計策,只要結(jié)果(成功的攻下西川)
public class Client {
public static void main(String[] args) {
OccupationContext context = new OccupationContext();
//這個給手下的人激勵不夠啊
context.occupationWestOfSichuan("拿下西川");
System.out.println("=========================");
//這個人人有賞,讓士兵有動力啊
context.occupationWestOfSichuan("拿下西川之后,人人有賞!");
}
}
控制臺輸出:
由于計劃泄露,上上計策失??!
楊懷、高沛是蜀中名將,手下有精銳部隊,而且據(jù)守關(guān)頭,我們可以裝作要回荊州,引他們輕騎來見,可就此將其擒殺,而后進兵成都,此為中計。
=========================
挑選精兵,晝夜兼行直接偷襲成都,可以一舉而定,此為上計計也!
我們上面的策略接口采用的是接口的形式來定義的,其實這個策略接口,是廣義上的接口,不是語言層面的interface,也可以是一個抽象類,如果多個算法具有公有的數(shù)據(jù),則可以將策略接口設(shè)計為一個抽象類,把公共的東西放到抽象類里面去。
策略和上下文的關(guān)系
在策略模式中,一般情況下都是上下文持有策略的引用,以進行對具體策略的調(diào)用。但具體的策略對象也可以從上下文中獲取所需數(shù)據(jù),可以將上下文當(dāng)做參數(shù)傳入到具體策略中,具體策略通過回調(diào)上下文中的方法來獲取其所需要的數(shù)據(jù)。
下面我們演示這種情況:
在跨國公司中,一般都會在各個國家和地區(qū)設(shè)置分支機構(gòu),聘用當(dāng)?shù)厝藶閱T工,這樣就有這樣一個需要:每月發(fā)工資的時候,中國國籍的員工要發(fā)人民幣,美國國籍的員工要發(fā)美元,英國國籍的要發(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 !");
}
}
//支付上下文,含有多個算法的公有數(shù)據(jù)
public class PayContext {
//員工姓名
private String username;
//員工的工資
private double money;
//支付策略
private PayStrategy payStrategy;
public void pay(){
//調(diào)用具體的支付策略來進行支付
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();
}
}
控制臺輸出:
現(xiàn)在給:小王 人民幣支付 30000.0元!
現(xiàn)在給:jack 美金支付 10000.0dollar !
那現(xiàn)在我們要新增一個銀行賬戶的支付策略,該怎么辦呢?
顯然我們應(yīng)該新增一個支付找銀行賬戶的策略實現(xiàn),由于需要從上下文中獲取數(shù)據(jù),為了不修改已有的上下文,我們可以通過繼承已有的上下文來擴展一個新的帶有銀行賬戶的上下文,然后再客戶端中使用新的策略實現(xiàn)和帶有銀行賬戶的上下文,這樣之前已有的實現(xiàn)完全不需要改動,遵守了開閉原則。
//銀行賬戶支付
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();
}
}
控制臺輸出:
現(xiàn)在給:小王 人民幣支付 30000.0元!
現(xiàn)在給:jack 美金支付 10000.0dollar !
現(xiàn)在給:小張的賬戶:1234567890 支付工資:40000.0 元!
除了上面的方法,還有其他的實現(xiàn)方式嗎?
當(dāng)然有了,上面的實現(xiàn)方式是策略實現(xiàn)所需要的數(shù)據(jù)都是從上下文中獲取,因此擴展了上下文;現(xiàn)在我們可以不擴展上下文,直接從策略實現(xiàn)內(nèi)部來獲取數(shù)據(jù),看下面的實現(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();
}
}
控制臺輸出:
現(xiàn)在給:小王 人民幣支付 30000.0元!
現(xiàn)在給:jack 美金支付 10000.0dollar !
現(xiàn)在給:小張的賬戶:1234567890 支付工資:40000.0 元!
那我們來比較一下上面兩種實現(xiàn)方式:
擴展上下文的實現(xiàn):
- 優(yōu)點:具體的策略實現(xiàn)風(fēng)格很是統(tǒng)一,策略實現(xiàn)所需要的數(shù)據(jù)都是從上下文中獲取的,在上下文中添加的數(shù)據(jù),可以視為公共的數(shù)據(jù),其他的策略實現(xiàn)也可以使用。
- 缺點:很明顯如果某些數(shù)據(jù)只是特定的策略實現(xiàn)需要,大部分的策略實現(xiàn)不需要,那這些數(shù)據(jù)有“浪費”之嫌,另外如果每次添加算法數(shù)據(jù)都擴展上下文,很容易導(dǎo)致上下文的層級很是復(fù)雜。
在具體的策略實現(xiàn)上添加所需要的數(shù)據(jù)的實現(xiàn):
- 優(yōu)點:容易想到,實現(xiàn)簡單
- 缺點:與其他的策略實現(xiàn)風(fēng)格不一致,其他的策略實現(xiàn)所需數(shù)據(jù)都是來自上下文,而這個策略實現(xiàn)一部分?jǐn)?shù)據(jù)來自于自身,一部分?jǐn)?shù)據(jù)來自于上下文;外部在使用這個策略實現(xiàn)的時候也和其他的策略實現(xiàn)不一致了,難以以一個統(tǒng)一的方式動態(tài)的切換策略實現(xiàn)。
策略模式在JDK中的應(yīng)用
在多線程編程中,我們經(jīng)常使用線程池來管理線程,以減緩線程頻繁的創(chuàng)建和銷毀帶來的資源的浪費,在創(chuàng)建線程池的時候,經(jīng)常使用一個工廠類來創(chuàng)建線程池Executors,實際上Executors的內(nèi)部使用的是類ThreadPoolExecutor.它有一個最終的構(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ù)干,也不會將其銷毀。
- maximumPoolSize:線程池中的最多能夠創(chuàng)建的線程數(shù)量。
- keepAliveTime:當(dāng)線程池中的線程數(shù)量大于corePoolSize時,多余的線程等待新任務(wù)的最長時間。
- unit:keepAliveTime的時間單位。
- workQueue:在線程池中的線程還沒有還得及執(zhí)行任務(wù)之前,保存任務(wù)的隊列(當(dāng)線程池中的線程都有任務(wù)在執(zhí)行的時候,仍然有任務(wù)不斷的提交過來,這些任務(wù)保存在workQueue隊列中)。
- threadFactory:創(chuàng)建線程池中線程的工廠。
- handler:當(dāng)線程池中沒有多余的線程來執(zhí)行任務(wù),并且保存任務(wù)的多列也滿了(指的是有界隊列),對仍在提交給線程池的任務(wù)的處理策略。
RejectedExecutionHandler 是一個策略接口,用在當(dāng)線程池中沒有多余的線程來執(zhí)行任務(wù),并且保存任務(wù)的多列也滿了(指的是有界隊列),對仍在提交給線程池的任務(wù)的處理策略。
線程池的具體介紹和實戰(zhàn),可以關(guān)注下公眾號Java技術(shù)棧,在后臺回復(fù):多線程,都是干貨。
public interface RejectedExecutionHandler {
/**
*當(dāng)ThreadPoolExecutor的execut方法調(diào)用時,并且ThreadPoolExecutor不能接受一個任務(wù)Task時,該方法就有可能被調(diào)用。
* 不能接受一個任務(wù)Task的原因:有可能是沒有多余的線程來處理,有可能是workqueue隊列中沒有多余的位置來存放該任務(wù),該方法有可能拋出一個未受檢的異常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);
}
該策略接口有四個實現(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ù)拋棄掉(對于提交的任務(wù)不管不問,什么也不做),不過并不拋出異常。
/**
* 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)閉時,從任務(wù)隊列workQueue中取出第一個任務(wù),并拋棄這第一個任務(wù),進而有空間存儲剛剛提交的任務(wù)。使用該策略要特別小心,因為它會直接拋棄之前的任務(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)沒有了多余的線程來分配該任務(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中持有一個RejectedExecutionHandler接口的引用,以便在構(gòu)造函數(shù)中可以由外部客戶端自己制定具體的策略并注入。下面看一下其類圖:

策略模式的優(yōu)點
策略模式的功能就是通過抽象、封裝來定義一系列的算法,使得這些算法可以相互替換,所以為這些算法定義一個公共的接口,以約束這些算法的功能實現(xiàn)。如果這些算法具有公共的功能,可以將接口變?yōu)槌橄箢悾瑢⒐补δ芊诺匠橄蟾割惱锩妗?/p>
策略模式的一系列算法是可以相互替換的、是平等的,寫在一起就是if-else組織結(jié)構(gòu),如果算法實現(xiàn)里又有條件語句,就構(gòu)成了多重條件語句,可以用策略模式,避免這樣的多重條件語句。
擴展性更好:在策略模式中擴展策略實現(xiàn)非常的容易,只要新增一個策略實現(xiàn)類,然后在使用策略實現(xiàn)的地方,使用這個新的策略實現(xiàn)就好了。
策略模式的缺點
1.客戶端必須了解所有的策略,清楚它們的不同:
如果由客戶端來決定使用何種算法,那客戶端必須知道所有的策略,清楚各個策略的功能和不同,這樣才能做出正確的選擇,但是這暴露了策略的具體實現(xiàn)。
2.增加了對象的數(shù)量:
由于策略模式將每個具體的算法都單獨封裝為一個策略類,如果可選的策略有很多的話,那對象的數(shù)量也會很多。
3.只適合偏平的算法結(jié)構(gòu):
由于策略模式的各個策略實現(xiàn)是平等的關(guān)系(可相互替換),實際上就構(gòu)成了一個扁平的算法結(jié)構(gòu)。即一個策略接口下面有多個平等的策略實現(xiàn)(多個策略實現(xiàn)是兄弟關(guān)系),并且運行時只能有一個算法被使用。這就限制了算法的使用層級,且不能被嵌套。
策略模式的本質(zhì)
分離算法,選擇實現(xiàn)。
如果你仔細(xì)思考策略模式的結(jié)構(gòu)和功能的話,就會發(fā)現(xiàn):如果沒有上下文,策略模式就回到了最基本的接口和實現(xiàn)了,只要是面向接口編程,就能夠享受到面向接口編程帶來的好處,通過一個統(tǒng)一的策略接口來封裝和分離各個具體的策略實現(xiàn),無需關(guān)系具體的策略實現(xiàn)。
貌似沒有上下文什么事,但是如果沒有上下文的話,客戶端就必須直接和具體的策略實現(xiàn)進行交互了,尤其是需要提供一些公共功能或者是存儲一些狀態(tài)的時候,會大大增加客戶端使用的難度;引入上下文之后,這部分工作可以由上下文來完成,客戶端只需要和上下文進行交互就可以了。這樣可以讓策略模式更具有整體性,客戶端也更加的簡單。
策略模式體現(xiàn)了開閉原則:策略模式把一系列的可變算法進行封裝,從而定義了良好的程序結(jié)構(gòu),在出現(xiàn)新的算法的時候,可以很容易的將新的算法實現(xiàn)加入到已有的系統(tǒng)中,而已有的實現(xiàn)不需要修改。
策略模式體現(xiàn)了里氏替換原則:策略模式是一個扁平的結(jié)構(gòu),各個策略實現(xiàn)都是兄弟關(guān)系,實現(xiàn)了同一個接口或者繼承了同一個抽象類。這樣只要使用策略的客戶端保持面向抽象編程,就可以動態(tài)的切換不同的策略實現(xiàn)以進行替換。
以上就是Java設(shè)計模式之策略模式示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Java策略模式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
ZooKeeper入門教程二在單機和集群環(huán)境下的安裝搭建及使用
本文是ZooKeeper入門系列教程,涵蓋ZooKeeper的安裝使及單機集群環(huán)境搭建,通過實例和大量圖表,結(jié)合實戰(zhàn),幫助學(xué)習(xí)者理解和運用,有需要的朋友可以借鑒參考下2022-01-01
SpringBoot整合log4j日志與HashMap的底層原理解析
這篇文章主要介紹了SpringBoot整合log4j日志與HashMap的底層原理,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-01-01
關(guān)于mybatis callSettersOnNulls 配置解析
這篇文章主要介紹了關(guān)于mybatis callSettersOnNulls 配置,非常不錯,具有一定的參考借鑒價值 ,需要的朋友可以參考下2018-06-06
Java中mkdir()和mkdirs()的區(qū)別及說明
這篇文章主要介紹了Java中mkdir()和mkdirs()的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11
Spring擴展BeanFactoryPostProcessor使用技巧詳解
這篇文章主要為大家介紹了Spring擴展BeanFactoryPostProcessor使用技巧詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09
Java中5種方式實現(xiàn)String反轉(zhuǎn)
下面小編就為大家?guī)硪黄狫ava中5種方式實現(xiàn)String反轉(zhuǎn)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。2016-06-06

