換個姿式學設計模式-策略模式

前言

最近段時間,接到一個需求:開發一個聚合支付服務,對其餘內部項目提供統一的接口來實現不一樣支付平臺的支付能力發起,好比支付寶,微信,銀聯等。爲了處理類似的支付操做而各平臺具體實現不一樣的狀況,要讓各個平臺接口能力能相互獨立,並要方便擴展後續新增的支付平臺,我引入了設計模式的策略模式來應對需求場景,藉此深刻學習總結下策略模式,因而也就有了本文,但願對學習策略模式的同窗有所幫助。html

爲何須要策略模式

平常工做開發中咱們總會遇到以下熟悉的代碼片斷:java

if(condition1){
 // do something1
} else if (condition2){
 // do something2
} else if (condition3){
 // do something3
}
複製代碼

在每一個 if 條件下都有數十行甚至百行的業務處理,各自處理又是相互獨立的而且目的一致,都匯聚在一個方法裏。這樣的寫法不但讓類變得臃腫冗長,而且不一樣邏輯都在一個類中修改,維護和擴展起來都很費勁。那麼又有什麼辦法能夠優化這大段的代碼呢,在實現功能的同時,讓代碼更加靈活和易維護。算法

要解決這個問題,本文的主角—策略模式 就登場了,做爲設計模式中比較簡單的行爲型模式,其實不少框架中都見到它的身影,稍後咱們也會從各框架源碼中識別策略模式的應用。使用策略模式能夠幫助咱們將每一個處理邏輯封裝成獨立的類,客戶端類須要進行哪一種處理邏輯就使用對應的類,調用其封裝了業務處理細節的方法便可。這樣一來,客戶端類減小了業務處理邏輯的大量代碼,讓自身更加精簡。當業務邏輯有所改動時,只要在對應的類中修改,而不影響其餘的類;而且若是出現了新的業務邏輯只要新增類似的類進行實現,供客戶端類調用便可。spring

什麼是策略模式

接下來咱們就介紹下策略模式的定義和組成,以及它的基本形式。設計模式

首先看下維基百科上策略模式的定義:微信

In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.app

策略模式也叫政策模式,容許程序在運行時選擇一個算法執行,一般存在一類算法實現提供外部選擇執行,這裏的算法,也能夠叫作策略,至關於上節內容提到的具體處理邏輯。框架

再來看下 《設計模式:可複用面向對象軟件的基礎》一書中對策略模式的定義:ide

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.函數

再次對其定義解讀:定義一類算法,各自獨立封裝實現,而且相互之間是可替換的。除此以外,由客戶端類決定具體使用哪一個算法。

上述兩個定義都提到了算法一詞,它表示了完整的,不可再拆分的業務邏輯處理。一般用接口或者抽象類來表示一類算法的抽象,提供多種對該類算法的操做實現,以此組成一類獨立且可替換的算法,也叫策略組。

瞭解完定義後,咱們再來看下策略模式通用類圖:

類圖中涉及三類角色:Context,Strategy 和 ConcreteStrategy

  • Strategy:抽象策略角色,表明某個算法的接口或者抽象類,定義了每一個算法或者策略須要具備的方法和屬性。
  • Context:上下文角色,引用策略接口對象,屏蔽了外部模塊對策略或方法的直接訪問,只能經過Context 提供的方法訪問。
  • ConcreteStrategy:抽象策略的具體實現,該類含有具體的算法,而且一般不僅一種實現,有多個類。

這三個角色的功能職責都十分明確,對應的源碼實現也十分簡單,如今咱們就來快速看下每一個角色對應的通用源碼。

// 抽象的策略角色
public interface Strategy {
    void doSomething();
}

// 具體策略角色
public class ConcreteStrategy implements Strategy {
    @Override
    public void doSomething() {
        System.out.println("ConcreteStrategy doSomething !");
    }
}

// 上下文角色
public class Context {
    private final Strategy strategy;

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

    public void doAnything() {
        this.strategy.doSomething();
    }
}
複製代碼

有了策略模式的基本代碼結構,在客戶端類中使用十分簡單,想要哪一個策略,就產生出它的具體策略對象放入上下文對象內,而後由上下文對象執行具體策略操做便可,具體代碼以下:

public class Client {
    public static void main(String[] args) {
        Strategy strategy = new ConcreteStrategy();
        Context context = new Context(strategy);
        context.doAnything(); // ConcreteStrategy doSomething !
    }
}
複製代碼

識別策略模式

看清楚了策略模式的定義,角色組成以及通用的代碼結構以後,咱們就來看下策略模式在通用框架裏的應用,來加深對策略模式的認識。

JDK 與策略模式

在經常使用的Java 集合框架中,比較器 java.util.Comparator 的設計就採用了策略模式。Comparator 就是一個抽象的策略接口,只要一個類實現這個接口,自定 compare 方法,該類成爲具體策略類,你能夠在不少地址找到這個抽象策略接口的實現,官方在工具類 java.util.Comparators 裏也提供 NaturalOrderComparator,NullComparator 兩種具體策略類。而使用 Comparator 到的 java.util.Collections 類就是 Context 角色,將集合的比較功能封裝成靜態方法對外提供。

Spring Framework 與策略模式

Spring 框架最先以 IoC 和 DI 兩大特性著稱,不須要開發者本身建立對象,而是經過 Spring IoC 容器識別而後實例化所需對象。在 Spring 中將執行建立對象實例的這個操做封裝爲一種算法,用接口類 org.springframework.beans.factory.support.InstantiationStrategy 進行聲明,而具體策略類則有 org.springframework.beans.factory.support.SimpleInstantiationStrategyorg.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy 兩個,而且 CglibSubclassingInstantiationStrategy 是對 SimpleInstantiationStrategy 的繼承擴展,也是 Spring 容器中真正使用到的策略類,具體應用的源碼可參考 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory 類:

/** * Instantiate the given bean using its default constructor. * @param beanName the name of the bean * @param mbd the bean definition for the bean * @return a BeanWrapper for the new instance */
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
    //...
    beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
    //...
}
複製代碼

如何使用策略模式

實例應用

俗話說學以至用,接觸了策略模式後咱們應該想一想怎麼用在本身平常開發項目中呢,這裏就簡單經過一個實例來講明下策略模式的使用方式。假設如今有個需求:須要對一個目錄或者文件實現兩種不一樣格式的解壓縮方式:zip壓縮和gzip壓縮,也後續可能新增其餘的解壓縮方式。

咱們首先將解壓縮的算法抽象成抽象策略接口 CompressStrategy, 提供壓縮方法 compress 和解壓縮方法 uncompress,分別接受源文件路徑和目的文件路徑。

策略類在命名一般上以 Strategy 爲後綴,來指明自身採用策略模式進行設計,以此簡化與其餘人溝通成本。

public interface CompressStrategy {
    public boolean compress(String source, String to);
    public boolean uncompress(String source, String to);
}
複製代碼

再對抽象策略接口進行實現,分別提供zip 壓縮算法和 gzip 壓縮算法,代碼以下:

public class ZipStrategy implements CompressStrategy {

    @Override
    public boolean compress(String source, String to) {
        System.out.println(source + " --> " + to + " ZIP壓縮成功!");
        return true;
    }

    @Override
    public boolean uncompress(String source, String to) {
        System.out.println(source + " --> " + to + " ZIP解壓縮成功!");
        return true;
    }
}

public class GzipStrategy implements CompressStrategy {

    @Override
    public boolean compress(String source, String to) {
        System.out.println(source + " --> " + to + " GZIP壓縮成功!");
        return true;
    }

    @Override
    public boolean uncompress(String source, String to) {
        System.out.println(source + " --> " + to + " GZIP解壓縮成功!");
        return true;
    }
}
複製代碼

代碼示例裏的實現爲了簡化只是簡單打印操做,具體實現能夠參考 JDK API 進行操做。

接下來看下 Context 角色的代碼實現:

public class CompressContext {

    private CompressStrategy compressStrategy;

    public CompressContext(CompressStrategy compressStrategy) {
        this.compressStrategy = compressStrategy;
    }

    public boolean compress(String source, String to) {
        return compressStrategy.compress(source, to);
    }

    public boolean uncompress(String source, String to) {
        return compressStrategy.uncompress(source, to);
    }
}
複製代碼

十分簡單,只是傳入一個具體算法,而後執行,到這裏標準的策略模式就編寫完畢了。客戶端類只是根據須要指定的具體壓縮策略對象傳給 CompressContext 對象便可。若是要新增一個壓縮算法,也只需對 CompressStrategy 接口提供新的實現便可傳給 CompressContext 對象使用。

public class Client {
    public static void main(String[] args) {
        CompressContext context;
        System.out.println("========執行算法========");
        context = new CompressContext(new ZipStrategy());
        context.compress("c:\\file", "d:\\file.zip");
        context.uncompress("c:\\file.zip", "d:\\file");
        System.out.println("========切換算法========");
        context = new CompressContext(new GzipStrategy());
        context.compress("c:\\file", "d:\\file.gzip");
        context.uncompress("c:\\file.gzip", "d:\\file");
    }
}
複製代碼

上面的策略模式的應用示例是否是很簡單,相似應用也有不少,好比要對接第三方支付,不一樣的支付平臺有不一樣的支付API,這個API操做均可以抽象成策略接口,客戶端發起特定平臺的支付接口時,咱們只需調用具體的支付策略類執行,而且每一個支付策略類相互獨立,可替換。

適用場景

本節最後簡單總結下策略模式的適用場景:

  • 若是一個對象有不少的行爲,它們的實現目的相同,而這些行爲使用了多重的條件選擇語句來實現。

  • 當一個系統須要動態地切換算法,會選擇一種算法去執行。

  • 客戶端類不須要知道具體算法的實現細節,只要調用並完成所須要求。

Lambda 與 策略模式

JDK 8 以後,利用Lambda能夠提供策略模式更加精簡的實現,若是策略接口是一個函數接口,那麼不須要聲明新的類來實現不一樣策略,直接經過傳遞Lambda就可實現,而且更加簡潔,具體使用方式參見下方代碼:

/** * Context 對象 */
public class Validator {
    private final ValidationStrategy strategy;

    public Validator(ValidationStrategy v) {
        this.strategy = v;
    }

    public boolean validate(String s) {
        return strategy.execute(s);
    }

}

/** * 策略接口 */
@FunctionalInterface
public interface ValidationStrategy {
    boolean execute(String s);
}

numericValidator = new Validator((String s) -> s.matches("[a-z]+"));
b1 = numericValidator.validate("aaaa"); // true
lowerCaseValidator = new Validator((String s) -> s.matches("\\d+"));
b2 = lowerCaseValidator.validate("bbbb"); // false
複製代碼

結合 Lambda 的策略模式更適合用於處理簡單算法操做的場景,若是算法實現複雜過於冗長複雜,仍是建議拆分紅單個類進行實現。

策略模式的注意點

策略模式使用起來雖然簡單,但它的靈活性在許多項目都能見到其身影,在使用時也有須要注意的地方,下面咱們就來看下:

  • 策略模式中每一個算法都是完整,不可拆分的原子業務,而且多個算法必須是能夠相互替換,,而用哪一個算法由外部調用者決定。

  • 當若是具體策略類超過4個,須要使用混合模式減小類膨脹和對外暴露的問題,經過其餘模式修正:工廠方法模式,代理模式,享元模式

策略模式的優缺點

一個設計模式的引入必存在它合理的地方和不足,最後咱們再說說下策略模式的優缺點。

優勢

  • 使用策略模式,能夠在不修改原有系統的基礎上更換算法或行爲,能夠靈活地增長新的算法或行爲,提供了系統的擴展性
  • 策略模式提供了對一類算法進行管理維護。
  • 使用策略模式能夠避免使用多重條件判斷,由外部模塊決定所要執行的策略類。

缺點

  • 客戶端必須知道全部的策略類,並自行決定使用哪個策略類。
  • 會產生不少策略類,使得類的項目增多。

結語

到這裏,本文對策略模式的學習就此結束,固然關於策略模式的內容遠不止這些,配合其餘模式還有用法,感興趣了的同窗能夠參考文末提供的資料連接進一步深刻學習。也歡迎掃碼關注微信公衆號:「聞人的技術博客」,按期分享Java技術乾貨,共同進步。

推薦閱讀

參考

本文由博客一文多發平臺 OpenWrite 發佈! -

相關文章
相關標籤/搜索