Java設計模式-策略模式

定義

Define a family of algorithms,encapsulate each one,and make them interchangeable.java

定義一組算法,將每一個算法都封裝起來,而且使它們之間能夠互換。git

策略模式使用的就是面向對象的繼承和多態機制,很是容易理解和掌握算法

實現

抽象策略

策略、算法家族的抽象,一般爲接口,也能夠是抽象類,定義每一個策略或算法必須具備的方法和屬性。設計模式

public interface Strategy {

    /** * 策略模式的運算法則 */
    void doSomething();
}
複製代碼

具體策略

實現抽象策略中的操做,該類含有具體的算法markdown

public class ConcreteStrategyA implements Strategy {
    @Override
    public void doSomething() {
        System.out.println("具體策略A的運算法則");
    }
}
複製代碼
public class ConcreteStrategyB implements Strategy {
    @Override
    public void doSomething() {
        System.out.println("具體策略B的運算法則");
    }
}
複製代碼

封裝類

也叫作上下文類或環境類,起承上啓下封裝做用,屏蔽高層模塊對策略、算法的直接訪問,封裝可能存在的變化。ide

策略模式的重點就是封裝角色,它是借用了代理模式的思路,和代理模式的差異就是策略模式的封裝角色和被封裝的策略類不用是同一個接口,若是是同一個接口那就成爲了代理模式。函數

public class Context {

    /** * 抽象策略 */
    private Strategy strategy;

    /** * 構造函數設置具體策略 * * @param strategy */
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    /** * 封裝後的策略方法 */
    public void executeStrategy() {
        this.strategy.doSomething();
    }
}
複製代碼

客戶端代碼

public class Client {

    public static void main(String[] args) {
        // 聲明一個具體的策略
        Strategy strategyA = new ConcreteStrategyA();
        // 聲明上下文對象
        Context contextA = new Context(strategyA);
        // 執行封裝後的方法
        contextA.executeStrategy();

        Strategy strategyB = new ConcreteStrategyB();
        Context contextB = new Context(strategyB);
        contextB.executeStrategy();
    }
}
複製代碼

優勢

  • 算法能夠自由切換oop

    這是策略模式自己定義的,只要實現抽象策略,它就成爲策略家族的一個成員,經過封裝角色對其進行封裝,保證對外提供「可自由切換」的策略。this

  • 避免使用多重條件判斷spa

    若是沒有策略模式,一個策略家族有5個策略算法,一會要使用A策略,一會要使用B策略,怎麼設計呢?使用多重的條件語句?多重條件語句不易維護,並且出錯的機率大大加強。使用策略模式後,能夠由其餘模塊決定採用何種策略,策略家族對外提供的訪問接口就是封裝類,簡化了操做,同時避免了條件語句判斷。

  • 擴展性良好

    在現有的系統中增長一個策略太容易了,只要實現接口就能夠了,其餘都不用修改,相似於一個可反覆拆卸的插件,這大大地符合了OCP原則。

缺點

  • 策略類數量增多

    每個策略都是一個類,複用的可能性很小,類數量增多。

  • 全部的策略類都須要對外暴露

    上層模塊必須知道有哪些策略,而後才能決定使用哪個策略,這與迪米特法則是相違背的(只是想使用一個策略,卻要要了解這個策略)。可使用其餘模式來修正這個缺陷,如工廠方法模式、代理模式或享元模式。

使用場景

  • 多個類只有在算法或行爲上稍有不一樣的場景。

  • 算法須要自由切換的場景。

    例如,算法的選擇是由使用者決定的,或者算法始終在進化,特別是一些站在技術前沿的行業,連業務專家都沒法給你保證這樣的系統規則可以存在多長時間,在這種狀況下策略模式是你最好的助手。

  • 須要屏蔽算法規則的場景。

    如今的科技發展得很快,人腦的記憶是有限的(就目前來講是有限的),太多的算法你只要知道一個名字就能夠了,傳遞相關的數字進來,反饋一個運算結果,萬事大吉。

注意事項

若是系統中的一個策略家族的具體策略數量超過4個,則須要考慮使用混合模式,解決策略類膨脹和對外暴露的問題,不然往後的系統維護就會成爲一個燙手山芋,誰都不想接。

最佳實踐

策略模式是一個很是簡單的模式(主要是用了Java繼承與多態的機制)。它在項目中使用得很是多,但單獨使用的地方就比較少了,由於它有致命缺陷:全部的策略都須要暴露出去,這樣才方便客戶端決定使用哪個策略。咱們的策略模式只是實現了策略的管理,可是沒有嚴格地定義「適當的場景」使用「適當的策略」,在實際項目中,通常經過工廠方法模式來實現策略類的聲明。

擴展(策略枚舉)

定義
  • 它是一個枚舉。
  • 它是一個濃縮了的策略模式的枚舉。

啥意思?來看代碼:

定義一個計算器(枚舉類)
public enum Calculator {
    PLUS("+") {
        public int exec(int x, int y) {
            return x + y;
        }
    },
    MINUS("-") {
        public int exec(int x, int y) {
            return x - y;
        }
    };

    private final String symbol;

    Calculator(String symbol) {
        this.symbol = symbol;
    }

    public String getSymbol() {
        return this.symbol;
    }

    /** * 聲明一個抽象方法 * 枚舉類型中的抽象方法必須被它的全部常量中的具體方法所覆蓋(被稱爲特定於常量的方法實現) */
    public abstract int exec(int a, int b);
}
複製代碼

把原有定義在抽象策略中的方法移植到枚舉中,每一個枚舉成員就成爲一個具體策略

客戶端代碼
public class Client {
    public static void main(String[] args) {
        int x = 100;
        int y = 10;
        System.out.println(x + " + " + y + " = " + Calculator.PLUS.exec(x, y));
        System.out.println(x + " - " + y + " = " + Calculator.MINUS.exec(x, y));
    }
}
複製代碼

代碼量很是少,並且還有一個顯著的優勢:真實地面向對象

Calculator.PLUS.exec(x, y)相似於「拿出計算器(Calculator),對x和y進行加法運算(MINUS),並馬上執行(exec)」,這與咱們平常接觸邏輯很是類似

策略枚舉是一個很是優秀和方便的模式(《Effective Java》中枚舉相關條目也有詳細介紹該模式),可是它受枚舉類型的限制,每一個枚舉項都是public、final、static的,擴展性受到了必定的約束,所以在系統開發中,策略枚舉通常擔當不常常發生變化的角色。

源碼地址:gitee.com/tianranll/j…

參考文獻:《設計模式之禪》、《Effective Java》

相關文章
相關標籤/搜索