欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java設(shè)計(jì)模式之策略模式示例詳解

 更新時(shí)間:2022年03月21日 09:54:18   作者:編程指南針  
策略模式屬于Java?23種設(shè)計(jì)模式中行為模式之一,該模式定義了一系列算法,并將每個(gè)算法封裝起來(lái),使它們可以相互替換,且算法的變化不會(huì)影響使用算法的客戶。本文將通過(guò)示例詳細(xì)講解這一模式,需要的可以參考一下

在講策略模式之前,我們先看一個(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入門教程二在單機(jī)和集群環(huán)境下的安裝搭建及使用

    本文是ZooKeeper入門系列教程,涵蓋ZooKeeper的安裝使及單機(jī)集群環(huán)境搭建,通過(guò)實(shí)例和大量圖表,結(jié)合實(shí)戰(zhàn),幫助學(xué)習(xí)者理解和運(yùn)用,有需要的朋友可以借鑒參考下
    2022-01-01
  • Java超詳細(xì)講解三大特性之一的封裝

    Java超詳細(xì)講解三大特性之一的封裝

    封裝是一個(gè)非常廣泛的概念,小到一個(gè)屬性的封裝,大到一個(gè)框架或者一個(gè)項(xiàng)目的封裝,下面這篇文章主要給大家介紹了關(guān)于java中封裝的那點(diǎn)事,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-05-05
  • SpringBoot整合log4j日志與HashMap的底層原理解析

    SpringBoot整合log4j日志與HashMap的底層原理解析

    這篇文章主要介紹了SpringBoot整合log4j日志與HashMap的底層原理,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • 關(guān)于mybatis callSettersOnNulls 配置解析

    關(guān)于mybatis callSettersOnNulls 配置解析

    這篇文章主要介紹了關(guān)于mybatis callSettersOnNulls 配置,非常不錯(cuò),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下
    2018-06-06
  • Java中mkdir()和mkdirs()的區(qū)別及說(shuō)明

    Java中mkdir()和mkdirs()的區(qū)別及說(shuō)明

    這篇文章主要介紹了Java中mkdir()和mkdirs()的區(qū)別及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • MyBatis-Plus的yml配置方式小結(jié)

    MyBatis-Plus的yml配置方式小結(jié)

    本文主要介紹了MyBatis-Plus的yml配置方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-11-11
  • 解決Map集合使用get方法返回null拋出空指針異常問(wèn)題

    解決Map集合使用get方法返回null拋出空指針異常問(wèn)題

    這篇文章主要介紹了解決Map集合使用get方法返回null拋出空指針異常問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Spring擴(kuò)展BeanFactoryPostProcessor使用技巧詳解

    Spring擴(kuò)展BeanFactoryPostProcessor使用技巧詳解

    這篇文章主要為大家介紹了Spring擴(kuò)展BeanFactoryPostProcessor使用技巧詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • Java中5種方式實(shí)現(xiàn)String反轉(zhuǎn)

    Java中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
  • java8之lambda表達(dá)式用法總結(jié)

    java8之lambda表達(dá)式用法總結(jié)

    這篇文章主要介紹了java8之lambda表達(dá)式用法總結(jié),需要的朋友可以參考下
    2020-02-02

最新評(píng)論