深入理解策略設計模式

目錄

一、what is 策略?

二、what is 策略模式?

2.1  策略模式中的角色

2.2  策略模式通用實現

2.3  案例改寫

三、策略模式核心思想

四、策略上下文角色的作用

4.1  從策略上下文獲取數據

4.2  策略實現類自己添加所需的數據

五、策略模式優缺點


一、what is 策略?

 

大家看到一堆if-else嵌套都會覺得很煩很low,策略設計模式可以幫你幹掉這些if-else,還給你清爽高逼格。

 

那什麼是策略,百度百科給了“策略”很精簡的解釋:

大白話就是完成目標的方式有很多,根據條件因地制宜選擇合適的方式,只要能完成目標就好。“條條大道通羅馬”也差不多是這個意思。 

 

二、what is 策略模式?

知道了策略是啥,策略模式又是啥?策略模式定義了一系列的算法,並將每一個算法封裝起來,使每個算法可以相互替代,使算法本身和使用算法的客戶端分割開來,相互獨立,根據不同的需求選擇不同的算法(實現方式)。它的中心是算法如何組織和調用。

上面的定義還是有些抽象,下面我們化抽象爲具體:

我們逛街喫飯結賬的時候應該都用過信用卡(沒用過信用卡的土豪忽略),商家對於不同銀行的信用卡的優惠力度往往是不同的,如果你去結賬,收銀員會根據你出示的信用卡選擇不同的優惠政策(中國銀行7折、農業銀行8折、農業銀行9折、其他銀行原價無優惠),不用策略模式我們可能會這樣寫:

/**
 * Feng, Ge 2020/3/11 0011 15:55
 */
public class OriginalDemo {

    private static Double originalPrice = 100.0;

    private static Double getDiscount(String bankName) {
        if ("中國銀行".equals(bankName)) {
            System.out.println("中國銀行7折!");
            return 0.7 * originalPrice;
        } else if ("農業銀行".equals(bankName)) {
            System.out.println("農業銀行8折!");
            return 0.8 * originalPrice;
        } else if ("光大銀行".equals(bankName)) {
            System.out.println("農業銀行9折!");
            return 0.9 * originalPrice;
        }
        return originalPrice;
    }

    public static void main(String[] args) {
        Double price = getDiscount("中國銀行");
        System.out.println("總共: " + price + " 元");
    }
}
中國銀行7折!
總共: 70.0 元

上面的代碼肯定是可以實現所需功能的,但是這個getDiscount()方法是把所有的優惠方式算法都寫在裏面了,若還有其他優惠形式,那就是不停的else-if,代碼顯得臃腫不簡潔(顯得low)。

那可能會這樣去改進:

/**
 * Feng, Ge 2020/3/11 0011 15:55
 */
public class OriginalBetterDemo {

    private static Double originalPrice = 100.0;

    private static Double getDiscount(String bankName) {
        if ("中國銀行".equals(bankName)) {
            return getBOC();
        } else if ("農業銀行".equals(bankName)) {
            return getABC();
        } else if ("光大銀行".equals(bankName)) {
            return getCEB();
        }
        return originalPrice;
    }

    private static Double getBOC(){
        System.out.println("中國銀行7折!");
        return 0.7 * originalPrice;
    }

    private static Double getABC(){
        System.out.println("農業銀行8折!");
        return 0.8 * originalPrice;
    }

    private static Double getCEB(){
        System.out.println("光大銀行9折!");
        return 0.9 * originalPrice;
    }

    public static void main(String[] args) {
        Double price = getDiscount("中國銀行");
        System.out.println("總共: " + price + " 元");
    }
}

現在是把各種優惠方式(算法)單獨抽取出來了,這個優化方式已經帶來了好處:當某個銀行的優惠力度改變時,只需要改變對應銀行的優惠算法即可,比如中國銀行現在優惠變成5折了,只需要修改getBOC()即可,不會影響別的銀行的優惠方式。

這個改進看着還不錯哈,但是假如現在商家要和工商合作了,給工商銀行信用卡的優惠爲6折,該怎麼辦?那就需要新增一個getICBC()方法,同時在getDiscount(String bankName) 方法中增加條件語句。這麼一處理,就會覺得好像哪裏不對勁,仔細一想這樣違背了開閉原則:對修改關閉,對擴展開放。擴展一個優惠對象除了新增算法,還需要改判斷邏輯,這顯然不是最好的處理方式。有沒有更好的處理方式呢?當然有,就是下面要說的策略模式。

 

2.1  策略模式中的角色

  1. 策略接口角色:用來約束一系列具體的策略算法,策略上下文角色ConcreteStrategy使用此策略接口來調用具體的策略所實現的算法。
  2. 具體策略實現角色:具體的策略實現,即具體的算法實現。
  3. 策略上下文角色Context:策略上下文,負責和具體的策略實現交互,通常策略上下文對象會持有一個真正的策略實現對象,策略上下文還可以讓具體的策略實現從其中獲取相關數據,回調策略上下文對象的方法。

實際上策略模式中每個算法對應一個具體策略實類,通過策略上下文類實現動態邏輯(持有誰就選擇誰的算法)。

 

2.2  策略模式通用實現

策略接口:

public interface StrategyInterface {
    void algorithm();
}

具體策略實現A:

public class ConcreteStrategyA implements StrategyInterface {
    @Override
    public void algorithm() {
        System.out.println("具體策略類A");
    }
}

具體策略實現B:

public class ConcreteStrategyB implements StrategyInterface {
    @Override
    public void algorithm() {
        System.out.println("具體策略類B");
    }
}

策略上下文類:

public class StrategyContext {
    private StrategyInterface strategy;

    public StrategyContext(StrategyInterface strategy) {
        this.strategy = strategy;
    }

    public void contextMethod(){
        strategy.algorithm();
    }
}

測試類:

public class CommonClient {
    public static void main(String[] args) {
        StrategyInterface strategy = new ConcreteStrategyA();
        StrategyContext context = new StrategyContext(strategy);
        context.contextMethod();
    }
}

2.3  案例改寫

知道了策略模式的主體結構就可以改寫最開始的例子了:

 策略接口:

/**
 * Feng, Ge 2020/3/12 0012 8:37
 */
public interface Strategy {
    Double discountAlgorithm(Double originalPrice);
}

具體策略實現ABC:

public class StrategyABC implements Strategy{

    @Override
    public Double discountAlgorithm(Double originalPrice) {
        System.out.println("農業銀行8折!");
        return 0.8 * originalPrice;
    }
}

具體策略實現BOC:

public class StrategyBOC implements Strategy{

    @Override
    public Double discountAlgorithm(Double originalPrice) {
        System.out.println("中國銀行7折!");
        return 0.7 * originalPrice;
    }
}

具體策略實現CEB:

public class StrategyCEB implements Strategy{

    @Override
    public Double discountAlgorithm(Double originalPrice) {
        System.out.println("光大銀行9折!");
        return 0.9 * originalPrice;
    }
}

策略上下文類:

public class Context {
    private Strategy strategy;
    private static Double originnalPrice = 100.0;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public Double getDiscount() {
        return strategy.discountAlgorithm(originnalPrice);
    }
}

測試類:

public class Client {
    public static void main(String[] args) {
        // 顧客出示農業銀行信用卡
        Strategy strategy = new StrategyABC();
        // 選擇農業銀行的優惠策略
        Context context = new Context(strategy);
        // 計算具體價格
        Double price = context.getDiscount();
        System.out.println("您共消費:" + price +"元!");
    }
}
農業銀行8折!
您共消費:80.0元!

這麼寫有什麼好處?那就是很好的踐行了“開閉原則”,易於擴展,不用改變原來的邏輯條件。比如現在商家要和工商合作了,給工商銀行信用卡的優惠爲6折,那隻需要增加工商銀行的具體策略實現類即可。其餘不用變:

public class StrategyICBC implements Strategy{

    @Override
    public Double discountAlgorithm(Double originalPrice) {
        System.out.println("工商銀行6折!");
        return 0.6 * originalPrice;
    }
}

這個在支付方式或者優惠方式很多很複雜的情況下,比如淘寶雙十一的各種優惠,要是用if--else去嵌套寫,估計沒人能看懂沒人能維護。

 

三、策略模式核心思想

通過上面的例子,可以進一步總結一下策略模式:

它就是把具體的算法實現從業務邏輯中剝離出來,成爲一系列獨立算法類,使得它們可以根據需要被動態選擇。

但是需要注意的是:策略模式的重心不是考慮算法如何實現,而是關心這些算法如何組織和調用。

 

四、策略上下文角色的作用

前面已經說過,策略上下文負責和具體的策略實現交互,通常策略上下文對象會持有一個真正的策略實現對象,策略上下文還可以讓具體的策略實現從其中獲取相關數據,回調策略上下文對象的方法。

實際上你也可以選擇去掉上下文類,把處理流程放在Client客戶端,如下:

public class ClientDemo {
    private static Strategy strategy;
    private static Double originnalPrice = 100.0;

    public static Double getDiscount() {
        return strategy.discountAlgorithm(originnalPrice);
    }

    public static void main(String[] args) {
        strategy = new StrategyABC();
        Double price = getDiscount();
        System.out.println("您共消費:" + price +"元!");
    }
}

雖然這樣也沒啥大問題,但仔細想想還是有問題的,在一個使用策略模式的系統中,當存在的策略很多時,客戶端管理所有策略算法將變得很複雜,如果在環境類中使用策略工廠模式來管理這些策略類將大大減少客戶端的工作複雜度,因此策略上下文這個角色還是很必要的。

4.1  從策略上下文獲取數據

“策略上下文還可以讓具體的策略實現從其中獲取相關數據,回調策略上下文對象的方法”是怎麼一回事?其實就是將上下文對象作爲參數傳遞到具體策略實現類中。例如我們在之前的例子中originalPrice是參數,現在我們可以把整個上下文對象都傳到具體策略實現類中:

public interface Strategy {
    Double discountAlgorithm(Context context);
}
public class StrategyABC implements Strategy {

    @Override
    public Double discountAlgorithm(Context context) {
        System.out.println("農業銀行8折!");
        System.out.println("您的卡號:" + context.getCardNo());
        return 0.8 * context.getOriginnalPrice();
    }
}
public class StrategyBOC implements Strategy {

    @Override
    public Double discountAlgorithm(Context context) {
        System.out.println("中國銀行7折!");
        System.out.println("您的卡號:" + context.getCardNo());
        return 0.7 * context.getOriginnalPrice();
    }
}
public class StrategyCEB implements Strategy{

    @Override
    public Double discountAlgorithm(Double originalPrice) {
        System.out.println("光大銀行9折!");
        return 0.9 * originalPrice;
    }
}

策略上下文:

public class Context {
    private Strategy strategy;
    private Double originnalPrice;
    private String cardNo;

    public String getCardNo() {
        return cardNo;
    }

    public Double getOriginnalPrice() {
        return originnalPrice;
    }

    public Context(Strategy strategy, Double originnalPrice, String cardNo) {
        this.strategy = strategy;
        this.originnalPrice = originnalPrice;
        this.cardNo = cardNo;
    }

    public Double getDiscount() {
        return strategy.discountAlgorithm(this);
    }
}
public class Client {
    public static void main(String[] args) {
        // 顧客出示農業銀行信用卡
        Strategy strategy = new StrategyABC();
        // 選擇農業銀行的優惠策略
        Context context = new Context(strategy, 100.0, "123456789");
        // 計算具體價格
        Double price = context.getDiscount();
        System.out.println("您共消費:" + price +"元!");
    }
}
農業銀行8折!
您的卡號:123456789
您共消費:80.0元!

這裏策略上下文類增加了銀行卡號,策略實現類中接收上下文對象,就可以取到客戶端構造上下文對象時傳入的值。

這樣做有什麼好處?策略實現所需要的數據都是從上下文中獲取的,在上下文中添加的數據,可以視爲公共的數據,其他的策略實現也可以使用;但是也有不好的地方,所有策略實現類共有一個上下文數據,當策略實現類很多時,上下文類會變得複雜,此外如果某些數據只是特定的策略實現需要,大部分的策略實現不需要,就會造成資源浪費。

另外如果每次添加算法數據都擴展上下文,很容易導致上下文的層級很是複雜。

 

4.2  策略實現類自己添加所需的數據

上面說到所有策略實現類統一從上下文中獲取數據是有缺陷的,某些策略實現類需要一些特定的數據但其他策略實現類不需要時可以在將數據直接添加在某些特定的策略實現類中,比如上面的卡號信息,可以定義在策略實現類中:

public class StrategyABC implements Strategy {
    private String cardNo;

    public StrategyABC(String cardNo) {
        this.cardNo = cardNo;
    }

    public void setCardNo(String cardNo) {
        this.cardNo = cardNo;
    }

    public String getCardNo() {
        return cardNo;
    }

    @Override
    public Double discountAlgorithm(Context context) {
        System.out.println("農業銀行8折!");
        System.out.println("您的卡號:" + cardNo);
        return 0.8 * context.getOriginnalPrice();
    }
}
public class Context {
    private Strategy strategy;
    private Double originnalPrice;

    public Double getOriginnalPrice() {
        return originnalPrice;
    }

    public Context(Strategy strategy, Double originnalPrice) {
        this.strategy = strategy;
        this.originnalPrice = originnalPrice;
    }

    public Double getDiscount() {
        return strategy.discountAlgorithm(this);
    }
}
public class Client {
    public static void main(String[] args) {
        // 顧客出示農業銀行信用卡
        Strategy strategy = new StrategyABC("123456");
        // 選擇農業銀行的優惠策略
        Context context = new Context(strategy, 100.0);
        // 計算具體價格
        Double price = context.getDiscount();
        System.out.println("您共消費:" + price +"元!");
    }
}
農業銀行8折!
您的卡號:123456
您共消費:80.0元!

 

 

五、策略模式優缺點

知道了策略模式的實現方式,下面討論下它的優缺點:

優點:

  1. 策略模式的一系列算法是可以相互替換的、是平等的,寫在一起就是if-else組織結構,如果算法實現裏又有條件語句,就構成了多重條件語句,可以用策略模式,避免這樣的多重條件語句。
  2. 擴展性更好:在策略模式中擴展策略實現非常的容易,只要新增一個策略實現類,然後在使用策略實現的地方,使用這個新的策略實現就好了。

缺點:

  1. 客戶端必須瞭解所有的策略,清楚它們的不同:如果由客戶端來決定使用何種算法,那客戶端必須知道所有的策略,清楚各個策略的功能和不同,這樣才能做出正確的選擇,但是這暴露了策略的具體實現。
  2. 增加了對象的數量:由於策略模式將每個具體的算法都單獨封裝爲一個策略類,如果可選的策略有很多的話,那對象的數量也會很多。
  3. 只適合偏平的算法結構:由於策略模式的各個策略實現是平等的關係(可相互替換),實際上就構成了一個扁平的算法結構。即一個策略接口下面有多個平等的策略實現(多個策略實現是兄弟關係),並且運行時只能有一個算法被使用。這就限制了算法的使用層級,且不能被嵌套。

 

策略模式體現了兩個是設計原則:

  • 開閉原則:策略模式把一系列的可變算法進行封裝,從而定義了良好的程序結構,在出現新的算法的時候,可以很容易的將新的算法實現加入到已有的系統中,而已有的實現不需要修改。
  • 里氏替換原則:里氏替換原則(LSP)指的是所有引用基類的地方都可以透明的使用其子類的對象。可以理解爲:只要有父類出現的地方,都可以使用子類來替代。而且不會出現任何錯誤或者異常(但是反過來卻不行。子類出現的地方,不能使用父類來替代)。策略模式是一個扁平的結構,各個策略實現都是兄弟關係,實現了同一個接口或者繼承了同一個抽象類。這樣只要使用策略的客戶端保持面向抽象編程,就可以動態的切換不同的策略實現以進行替換。

ps:里氏替換原則

     具體約束

  1. 子類必須實現父類的抽象方法,但不得重寫父類的非抽象(已實現的)方法。
  2. 子類中可增加自己特有的方法。(可以隨時擴展)
  3. 當子類覆蓋或者實現父類的方法時,方法的前置條件(方法形參)要比父類輸入參數更加寬鬆。否則會調用到父類的方法。
  4. 當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的返回值)要比父類更嚴格。否則會調用到父類的方法。

我們最好將父類定義爲抽象類,並定義抽象方法,讓子類重新定義這些方法,當父類是抽象類時候,父類不能實例化。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章