首頁 > 成語

詳解設計模式之策略模式

作者:由 指南針畢業設計 發表于 成語日期:2022-04-23

閉什麼結構

詳解設計模式之策略模式

在講策略模式之前,我們先看一個日常生活中的小例子:

現實生活中我們到商場買東西的時候,賣場往往根據不同的客戶制定不同的報價策略,比如針對新客戶不打折扣,針對老客戶打9折,針對VIP客戶打8折。。。

現在我們要做一個報價管理的模組,簡要點就是要針對不同的客戶,提供不同的折扣報價。

如果是有你來做,你會怎麼做?

我們很有可能寫出下面的程式碼:

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; }}

經過測試,上面的程式碼工作的很好,可是上面的程式碼是有問題的。上面存在的問題:

把不同客戶的報價的演算法都放在了同一個方法裡面,使得該方法很是龐大

(現在是隻是一個演示,所以看起來還不是很臃腫)。

下面看一下上面的改進,我們把不同客戶的報價的演算法都單獨作為一個方法

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; }}

上面的程式碼比剛開始的時候要好一點,它把每個具體的演算法都單獨抽出來作為一個方法,當某一個具體的演算法有了變動的時候,只需要修改響應的報價演算法就可以了。

但是改進後的程式碼還是有問題的,那有什麼問題呢?

1。當我們新增一個客戶型別的時候,首先要新增一個該種客戶型別的報價演算法方法,然後再quote方法中再加一個else if的分支,是不是感覺很是麻煩呢?而且這也違反了設計原則之一的開閉原則(open-closed-principle)。

開閉原則:

對於擴充套件是開放的(Open for extension)。這意味著模組的行為是可以擴充套件的。當應用的需求改變時,我們可以對模組進行擴充套件,使其具有滿足那些改變的新行為。也就是說,我們可以改變模組的功能。

對於修改是關閉的(Closed for modification)。對模組行為進行擴充套件時,不必改動模組的原始碼或者二進位制程式碼。

2。我們經常會面臨這樣的情況,不同的時期使用不同的報價規則,比如在各個節假日舉行的各種促銷活動時、商場店慶時往往都有普遍的折扣,但是促銷時間一旦過去,報價就要回到正常價格上來。按照上面的程式碼我們就得修改if else裡面的程式碼很是麻煩

那有沒有什麼辦法使得我們的報價管理即可擴充套件、可維護,又可以方便的響應變化呢?當然有解決方案啦,就是我們下面要講的策略模式。

定義:

策略模式定義了一系列的演算法,並將每一個演算法封裝起來,使每個演算法可以相互替代,使演算法本身和使用演算法的客戶端分割開來,相互獨立。

結構:

策略介面角色IStrategy:用來約束一系列具體的策略演算法,策略上下文角色ConcreteStrategy使用此策略介面來呼叫具體的策略所實現的演算法。

具體策略實現角色ConcreteStrategy:具體的策略實現,即具體的演算法實現。

策略上下文角色StrategyContext:策略上下文,負責和具體的策略實現互動,通常策略上下文物件會持有一個真正的策略實現物件,策略上下文還可以讓具體的策略實現從其中獲取相關資料,回撥策略上下文物件的方法。

UML類圖:

詳解設計模式之策略模式

UML序列圖:

詳解設計模式之策略模式

策略模式程式碼的一般通用實現:

策略介面

package strategy。examp01;//策略介面public interface IStrategy { //定義的抽象演算法方法 來約束具體的演算法實現方法 public void algorithmMethod();}

具體的策略實現:

package strategy。examp01;// 具體的策略實現public class ConcreteStrategy implements IStrategy { //具體的演算法實現 @Override public void algorithmMethod() { System。out。println(“this is ConcreteStrategy method。。。”); }}

package strategy。examp01; // 具體的策略實現2public class ConcreteStrategy2 implements IStrategy { //具體的演算法實現 @Override public void algorithmMethod() { System。out。println(“this is ConcreteStrategy2 method。。。”); }}

策略上下文:

package strategy。examp01;/** * 策略上下文 */public class StrategyContext { //持有一個策略實現的引用 private IStrategy strategy; //使用構造器注入具體的策略類 public StrategyContext(IStrategy strategy) { this。strategy = strategy; } public void contextMethod(){ //呼叫策略實現的方法 strategy。algorithmMethod(); }}

外部客戶端:

package strategy。examp01;//外部客戶端public class Client { public static void main(String[] args) { //1。建立具體測策略實現 IStrategy strategy = new ConcreteStrategy2(); //2。在建立策略上下文的同時,將具體的策略實現物件注入到策略上下文當中 StrategyContext ctx = new StrategyContext(strategy); //3。呼叫上下文物件的方法來完成對具體策略實現的回撥 ctx。contextMethod(); }}

針對我們一開始講的報價管理的例子:我們可以應用策略模式對其進行改造,不同型別的客戶有不同的折扣,我們可以將不同型別的客戶的報價規則都封裝為一個獨立的演算法,然後抽象出這些報價演算法的公共介面

公共報價策略介面:

package strategy。examp02;import java。math。BigDecimal;//報價策略介面public interface IQuoteStrategy { //獲取折後價的價格 BigDecimal getPrice(BigDecimal originalPrice);}

新客戶報價策略實現:

package strategy。examp02;import java。math。BigDecimal;//新客戶的報價策略實現類public class NewCustomerQuoteStrategy implements IQuoteStrategy { @Override public BigDecimal getPrice(BigDecimal originalPrice) { System。out。println(“抱歉!新客戶沒有折扣!”); return originalPrice; }}

老客戶報價策略實現:

package strategy。examp02;import java。math。BigDecimal;//老客戶的報價策略實現public class OldCustomerQuoteStrategy implements IQuoteStrategy { @Override public BigDecimal getPrice(BigDecimal originalPrice) { System。out。println(“恭喜!老客戶享有9折優惠!”); originalPrice = originalPrice。multiply(new BigDecimal(0。9))。setScale(2,BigDecimal。ROUND_HALF_UP); return originalPrice; }}

VIP客戶報價策略實現:

package strategy。examp02;import java。math。BigDecimal;//VIP客戶的報價策略實現public class VIPCustomerQuoteStrategy implements IQuoteStrategy { @Override public BigDecimal getPrice(BigDecimal originalPrice) { System。out。println(“恭喜!VIP客戶享有8折優惠!”); 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; } //回撥具體報價策略的方法 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。建立老客戶的報價策略 IQuoteStrategy oldQuoteStrategy = new OldCustomerQuoteStrategy(); //2。建立報價上下文物件,並設定具體的報價策略 QuoteContext quoteContext = new QuoteContext(oldQuoteStrategy); //3。呼叫報價上下文的方法 BigDecimal price = quoteContext。getPrice(new BigDecimal(100)); System。out。println(“折扣價為:” +price); }}

控制檯輸出:

恭喜!老客戶享有9折優惠!折扣價為:90。00

這個時候,商場營銷部新推出了一個客戶型別——MVP使用者(Most Valuable Person),可以享受折扣7折優惠,那該怎麼辦呢?

這個很容易,只要新增一個報價策略的實現,然後外部客戶端呼叫的時候,建立這個新增的報價策略實現,並設定到策略上下文就可以了,對原來已經實現的程式碼沒有任何的改動。

MVP使用者的報價策略實現:

package strategy。examp02;import java。math。BigDecimal;//MVP客戶的報價策略實現public class MVPCustomerQuoteStrategy implements IQuoteStrategy { @Override public BigDecimal getPrice(BigDecimal originalPrice) { System。out。println(“哇偶!MVP客戶享受7折優惠!!!”); 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) { //建立MVP客戶的報價策略 IQuoteStrategy mvpQuoteStrategy = new MVPCustomerQuoteStrategy(); //建立報價上下文物件,並設定具體的報價策略 QuoteContext quoteContext = new QuoteContext(mvpQuoteStrategy); //呼叫報價上下文的方法 BigDecimal price = quoteContext。getPrice(new BigDecimal(100)); System。out。println(“折扣價為:” +price); }}

控制檯輸出:

哇偶!MVP客戶享受7折優惠!!!折扣價為:70。00

深入理解策略模式:

策略模式的作用:就是把具體的演算法實現從業務邏輯中剝離出來,成為一系列獨立演算法類,使得它們可以相互替換。

策略模式的著重點:不是如何來實現演算法,而是如何組織和呼叫這些演算法,從而讓我們的程式結構更加的靈活、可擴充套件。

我們前面的第一個報價管理的示例,發現每個策略演算法實現對應的都是在QuoteManager 中quote方法中的if else語句裡面,我們知道if else if語句裡面的程式碼在執行的可能性方面可以說是平等的,你要麼執行if,要麼執行else,要麼執行else if。

策略模式就是把各個平等的具體實現進行抽象、封裝成為獨立的演算法類,然後透過上下文和具體的演算法類來進行互動。各個策略演算法都是平等的,地位是一樣的,正是由於各個演算法的平等性,所以它們才是可以相互替換的。雖然我們可以動態的切換各個策略,但是同一時刻只能使用一個策略。

在這個點上,我們舉個歷史上有名的故事作為示例:

三國劉備取西川時,謀士龐統給的上、中、下三個計策:

上策:挑選精兵,晝夜兼行直接偷襲成都,可以一舉而定,此為上計計也。

中策:楊懷、高沛是蜀中名將,手下有精銳部隊,而且據守關頭,我們可以裝作要回荊州,引他們輕騎來見,可就此將其擒殺,而後進兵成都,此為中計。

下策:退還白帝,連引荊州,慢慢進圖益州,此為下計。

這三個計策都是取西川的計策,也就是攻取西川這個問題的具體的策略演算法,劉備可以採用上策,可以採用中策,當然也可以採用下策,由此可見策略模式的各種具體的策略演算法都是平等的,可以相互替換。

那誰來選擇具體採用哪種計策(演算法)?

在這個故事中當然是劉備選擇了,也就是外部的客戶端選擇使用某個具體的演算法,然後把該演算法(計策)設定到上下文當中;

還有一種情況就是客戶端不選擇具體的演算法,把這個事交給上下文,這相當於劉備說我不管有哪些攻取西川的計策,我只要結果(成功的拿下西川),具體怎麼攻佔(有哪些計策,怎麼選擇)由參謀部來決定(上下文)。

下面我們演示下這種情景:

//攻取西川的策略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) { //故意設定障礙,導致上上計策失敗 System。out。println(“由於計劃洩露,上上計策失敗!”); int i = 100/0; } System。out。println(“挑選精兵,晝夜兼行直接偷襲成都,可以一舉而定,此為上計計也!”); }}

//攻取西川的中計策public class MiddleStrategy implements IOccupationStrategyWestOfSiChuan { @Override public void occupationWestOfSiChuan(String msg) { System。out。println(“楊懷、高沛是蜀中名將,手下有精銳部隊,而且據守關頭,我們可以裝作要回荊州,引他們輕騎來見,可就此將其擒殺,而後進兵成都,此為中計。”); }}

//攻取西川的下計策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); } }}

//此時外部客戶端相當於劉備了,不管具體採用什麼計策,只要結果(成功的攻下西川)public class Client { public static void main(String[] args) { OccupationContext context = new OccupationContext(); //這個給手下的人激勵不夠啊 context。occupationWestOfSichuan(“拿下西川”); System。out。println(“=========================”); //這個人人有賞,讓士兵有動力啊 context。occupationWestOfSichuan(“拿下西川之後,人人有賞!”); }}

控制檯輸出:

由於計劃洩露,上上計策失敗!楊懷、高沛是蜀中名將,手下有精銳部隊,而且據守關頭,我們可以裝作要回荊州,引他們輕騎來見,可就此將其擒殺,而後進兵成都,此為中計。=========================挑選精兵,晝夜兼行直接偷襲成都,可以一舉而定,此為上計計也!

我們上面的策略介面採用的是介面的形式來定義的,其實這個策略介面,是廣義上的介面,不是語言層面的interface,也可以是一個抽象類,如果多個演算法具有公有的資料,則可以將策略介面設計為一個抽象類,把公共的東西放到抽象類裡面去。

策略和上下文的關係:

在策略模式中,一般情況下都是上下文持有策略的引用,以進行對具體策略的呼叫。但具體的策略物件也可以從上下文中獲取所需資料,可以將上下文當做引數傳入到具體策略中,具體策略透過回撥上下文中的方法來獲取其所需要的資料。

下面我們演示這種情況:

在跨國公司中,一般都會在各個國家和地區設定分支機構,聘用當地人為員工,這樣就有這樣一個需要:每月發工資的時候,中國國籍的員工要發人民幣,美國國籍的員工要發美元,英國國籍的要發英鎊。

//支付策略介面public interface PayStrategy { //在支付策略介面的支付方法中含有支付上下文作為引數,以便在具體的支付策略中回撥上下文中的方法獲取資料 public void pay(PayContext ctx);}

//人民幣支付策略public class RMBPay implements PayStrategy { @Override public void pay(PayContext ctx) { System。out。println(“現在給:”+ctx。getUsername()+“ 人民幣支付 ”+ctx。getMoney()+“元!”); }}

//美金支付策略public class DollarPay implements PayStrategy { @Override public void pay(PayContext ctx) { System。out。println(“現在給:”+ctx。getUsername()+“ 美金支付 ”+ctx。getMoney()+“dollar !”); }}

//支付上下文,含有多個演算法的公有資料public class PayContext { //員工姓名 private String username; //員工的工資 private double money; //支付策略 private PayStrategy payStrategy; public void pay(){ //呼叫具體的支付策略來進行支付 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) { //建立具體的支付策略 PayStrategy rmbStrategy = new RMBPay(); PayStrategy dollarStrategy = new DollarPay(); //準備小王的支付上下文 PayContext ctx = new PayContext(“小王”,30000,rmbStrategy); //向小王支付工資 ctx。pay(); //準備Jack的支付上下文 ctx = new PayContext(“jack”,10000,dollarStrategy); //向Jack支付工資 ctx。pay(); }}

控制檯輸出:

現在給:小王 人民幣支付 30000。0元!現在給:jack 美金支付 10000。0dollar !

那現在我們要新增一個銀行賬戶的支付策略,該怎麼辦呢?

顯然我們應該新增一個支付找銀行賬戶的策略實現,由於需要從上下文中獲取資料,為了不修改已有的上下文,我們可以透過繼承已有的上下文來擴充套件一個新的帶有銀行賬戶的上下文,然後再客戶端中使用新的策略實現和帶有銀行賬戶的上下文,這樣之前已有的實現完全不需要改動,遵守了開閉原則。

//銀行賬戶支付public class AccountPay implements PayStrategy { @Override public void pay(PayContext ctx) { PayContextWithAccount ctxAccount = (PayContextWithAccount) ctx; System。out。println(“現在給:”+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) { //建立具體的支付策略 PayStrategy rmbStrategy = new RMBPay(); PayStrategy dollarStrategy = new DollarPay(); //準備小王的支付上下文 PayContext ctx = new PayContext(“小王”,30000,rmbStrategy); //向小王支付工資 ctx。pay(); //準備Jack的支付上下文 ctx = new PayContext(“jack”,10000,dollarStrategy); //向Jack支付工資 ctx。pay(); //建立支付到銀行賬戶的支付策略 PayStrategy accountStrategy = new AccountPay(); //準備帶有銀行賬戶的上下文 ctx = new PayContextWithAccount(“小張”,40000,accountStrategy,“1234567890”); //向小張的賬戶支付 ctx。pay(); }}

控制檯輸出:

現在給:小王 人民幣支付 30000。0元!現在給:jack 美金支付 10000。0dollar !現在給:小張的賬戶:1234567890 支付工資:40000。0 元!

除了上面的方法,還有其他的實現方式嗎?

當然有了,上面的實現方式是策略實現所需要的資料都是從上下文中獲取,因此擴充套件了上下文;現在我們可以不擴充套件上下文,直接從策略實現內部來獲取資料,看下面的實現:

//支付到銀行賬戶的策略public class AccountPay2 implements PayStrategy { //銀行賬戶 private String account; public AccountPay2(String account) { this。account = account; } @Override public void pay(PayContext ctx) { System。out。println(“現在給:”+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) { //建立具體的支付策略 PayStrategy rmbStrategy = new RMBPay(); PayStrategy dollarStrategy = new DollarPay(); //準備小王的支付上下文 PayContext ctx = new PayContext(“小王”,30000,rmbStrategy); //向小王支付工資 ctx。pay(); //準備Jack的支付上下文 ctx = new PayContext(“jack”,10000,dollarStrategy); //向Jack支付工資 ctx。pay(); //建立支付到銀行賬戶的支付策略 PayStrategy accountStrategy = new AccountPay2(“1234567890”); //準備上下文 ctx = new PayContext(“小張”,40000,accountStrategy); //向小張的賬戶支付 ctx。pay(); }}

控制檯輸出:

現在給:小王 人民幣支付 30000。0元!現在給:jack 美金支付 10000。0dollar !現在給:小張的賬戶:1234567890 支付工資:40000。0 元!

那我們來比較一下上面兩種實現方式:

擴充套件上下文的實現:

優點:具體的策略實現風格很是統一,策略實現所需要的資料都是從上下文中獲取的,在上下文中新增的資料,可以視為公共的資料,其他的策略實現也可以使用。

缺點:很明顯如果某些資料只是特定的策略實現需要,大部分的策略實現不需要,那這些資料有“浪費”之嫌,另外如果每次新增演算法資料都擴充套件上下文,很容易導致上下文的層級很是複雜。

在具體的策略實現上新增所需要的資料的實現:

優點:容易想到,實現簡單

缺點:與其他的策略實現風格不一致,其他的策略實現所需資料都是來自上下文,而這個策略實現一部分資料來自於自身,一部分資料來自於上下文;外部在使用這個策略實現的時候也和其他的策略實現不一致了,難以以一個統一的方式動態的切換策略實現。

策略模式在JDK中的應用:

在多執行緒程式設計中,我們經常使用執行緒池來管理執行緒,以減緩執行緒頻繁的建立和銷燬帶來的資源的浪費,在建立執行緒池的時候,經常使用一個工廠類來建立執行緒池Executors,實際上Executors的內部使用的是類ThreadPoolExecutor。它有一個最終的建構函式如下:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue 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:執行緒池中的核心執行緒數量,即使這些執行緒沒有任務幹,也不會將其銷燬。

maximumPoolSize:執行緒池中的最多能夠建立的執行緒數量。

keepAliveTime:當執行緒池中的執行緒數量大於corePoolSize時,多餘的執行緒等待新任務的最長時間。

unit:keepAliveTime的時間單位。

workQueue:線上程池中的執行緒還沒有還得及執行任務之前,儲存任務的佇列(當執行緒池中的執行緒都有任務在執行的時候,仍然有任務不斷的提交過來,這些任務儲存在workQueue佇列中)。

threadFactory:建立執行緒池中執行緒的工廠。

handler:當執行緒池中沒有多餘的執行緒來執行任務,並且儲存任務的多列也滿了(指的是有界佇列),對仍在提交給執行緒池的任務的處理策略。

RejectedExecutionHandler 是一個策略介面,用在當執行緒池中沒有多餘的執行緒來執行任務,並且儲存任務的多列也滿了(指的是有界佇列),對仍在提交給執行緒池的任務的處理策略。

執行緒池的具體介紹和實戰,可以關注下公眾號Java技術棧,在後臺回覆:多執行緒,都是乾貨。

public interface RejectedExecutionHandler { /** *當ThreadPoolExecutor的execut方法呼叫時,並且ThreadPoolExecutor不能接受一個任務Task時,該方法就有可能被呼叫。 * 不能接受一個任務Task的原因:有可能是沒有多餘的執行緒來處理,有可能是workqueue佇列中沒有多餘的位置來存放該任務,該方法有可能丟擲一個未受檢的異常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);}

該策略介面有四個實現類:

AbortPolicy:該策略是直接將提交的任務拋棄掉,並丟擲RejectedExecutionException異常。

/** * A handler for rejected tasks that throws a * RejectedExecutionException。 */ public static class AbortPolicy implements RejectedExecutionHandler { /** * Creates an AbortPolicy。 */ 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:該策略也是將任務拋棄掉(對於提交的任務不管不問,什麼也不做),不過並不丟擲異常。

/** * A handler for rejected tasks that silently discards the * rejected task。 */ public static class DiscardPolicy implements RejectedExecutionHandler { /** * Creates a DiscardPolicy。 */ 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:該策略是當執行器未關閉時,從任務佇列workQueue中取出第一個任務,並拋棄這第一個任務,進而有空間儲存剛剛提交的任務。使用該策略要特別小心,因為它會直接拋棄之前的任務。

/** * A handler for rejected tasks that discards the oldest unhandled * request and then retries execute, unless the executor * is shut down, in which case the task is discarded。 */ public static class DiscardOldestPolicy implements RejectedExecutionHandler { /** * Creates a DiscardOldestPolicy 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:該策略並沒有拋棄任何的任務,由於執行緒池中已經沒有了多餘的執行緒來分配該任務,該策略是在當前執行緒(呼叫者執行緒)中直接執行該任務。

/** * 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介面的引用,以便在建構函式中可以由外部客戶端自己制定具體的策略並注入。下面看一下其類圖:

詳解設計模式之策略模式

策略模式的優點:

策略模式的功能就是透過抽象、封裝來定義一系列的演算法,使得這些演算法可以相互替換,所以為這些演算法定義一個公共的介面,以約束這些演算法的功能實現。如果這些演算法具有公共的功能,可以將介面變為抽象類,將公共功能放到抽象父類裡面。

策略模式的一系列演算法是可以相互替換的、是平等的,寫在一起就是if-else組織結構,如果演算法實現裡又有條件語句,就構成了多重條件語句,可以用策略模式,避免這樣的多重條件語句。

擴充套件性更好:在策略模式中擴充套件策略實現非常的容易,只要新增一個策略實現類,然後在使用策略實現的地方,使用這個新的策略實現就好了。

策略模式的缺點:

1.客戶端必須瞭解所有的策略,清楚它們的不同:

如果由客戶端來決定使用何種演算法,那客戶端必須知道所有的策略,清楚各個策略的功能和不同,這樣才能做出正確的選擇,但是這暴露了策略的具體實現。

2.增加了物件的數量:

由於策略模式將每個具體的演算法都單獨封裝為一個策略類,如果可選的策略有很多的話,那物件的數量也會很多。

3.只適合偏平的演算法結構:

由於策略模式的各個策略實現是平等的關係(可相互替換),實際上就構成了一個扁平的演算法結構。即一個策略介面下面有多個平等的策略實現(多個策略實現是兄弟關係),並且執行時只能有一個演算法被使用。這就限制了演算法的使用層級,且不能被巢狀。

策略模式的本質:

分離演算法,選擇實現。

如果你仔細思考策略模式的結構和功能的話,就會發現:如果沒有上下文,策略模式就回到了最基本的介面和實現了,只要是面向介面程式設計,就能夠享受到面向介面程式設計帶來的好處,透過一個統一的策略介面來封裝和分離各個具體的策略實現,無需關係具體的策略實現。

貌似沒有上下文什麼事,但是如果沒有上下文的話,客戶端就必須直接和具體的策略實現進行互動了,尤其是需要提供一些公共功能或者是儲存一些狀態的時候,會大大增加客戶端使用的難度;引入上下文之後,這部分工作可以由上下文來完成,客戶端只需要和上下文進行互動就可以了。這樣可以讓策略模式更具有整體性,客戶端也更加的簡單。

策略模式體現了開閉原則:策略模式把一系列的可變演算法進行封裝,從而定義了良好的程式結構,在出現新的演算法的時候,可以很容易的將新的演算法實現加入到已有的系統中,而已有的實現不需要修改。

策略模式體現了里氏替換原則:策略模式是一個扁平的結構,各個策略實現都是兄弟關係,實現了同一個介面或者繼承了同一個抽象類。這樣只要使用策略的客戶端保持面向抽象程式設計,就可以動態的切換不同的策略實現以進行替換。