主頁 > 後端開發 > 20,000 字幫你搞定策略模式!

20,000 字幫你搞定策略模式!

2020-09-14 04:56:12 後端開發

作者:路易小七
來源:www.cnblogs.com/lewis0077/p/5133812.html


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

現實生活中我們到商場買東西的時候,賣場往往根據不同的客戶制定不同的報價策略,比如針對新客戶不打折扣,針對老客戶打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里面的代碼很是麻煩

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

定義:

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

結構:

  1. 策略介面角色IStrategy:用來約束一系列具體的策略演算法,策略背景關系角色ConcreteStrategy使用此策略介面來呼叫具體的策略所實作的演算法,

  2. 具體策略實作角色ConcreteStrategy:具體的策略實作,即具體的演算法實作,

  3. 策略背景關系角色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;

 // 具體的策略實作2
public 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<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:執行緒池中的核心執行緒數量,即使這些執行緒沒有任務干,也不會將其銷毀,

  • 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
     * <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:該策略也是將任務拋棄掉(對于提交的任務不管不問,什么也不做),不過并不拋出例外,

/**
     * 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:該策略是當執行器未關閉時,從任務佇列workQueue中取出第一個任務,并拋棄這第一個任務,進而有空間存盤剛剛提交的任務,使用該策略要特別小心,因為它會直接拋棄之前的任務,

/**
     * 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:該策略并沒有拋棄任何的任務,由于執行緒池中已經沒有了多余的執行緒來分配該任務,該策略是在當前執行緒(呼叫者執行緒)中直接執行該任務,

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

策略模式的優點:

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

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

  3. 擴展性更好:在策略模式中擴展策略實作非常的容易,只要新增一個策略實作類,然后在使用策略實作的地方,使用這個新的策略實作就好了,

策略模式的缺點:

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

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

2.增加了物件的數量:

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

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

由于策略模式的各個策略實作是平等的關系(可相互替換),實際上就構成了一個扁平的演算法結構,即一個策略介面下面有多個平等的策略實作(多個策略實作是兄弟關系),并且運行時只能有一個演算法被使用,這就限制了演算法的使用層級,且不能被嵌套,

策略模式的本質:

分離演算法,選擇實作,

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

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

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

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

推薦去我的博客閱讀更多:

1.Java JVM、集合、多執行緒、新特性系列教程

2.Spring MVC、Spring Boot、Spring Cloud 系列教程

3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程

4.Java、后端、架構、阿里巴巴等大廠最新面試題

覺得不錯,別忘了點贊+轉發哦!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/30804.html

標籤:Java

上一篇:求助大神!關于C++作業

下一篇:問題

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more