設計模式——策略模式

騰龍問天發表於2021-02-28
1.策略模式
定義一系列演算法,把他們獨立封裝起來,並且這些演算法之間可以相互替換。策略模式主要是管理一堆有共性的演算法,策略模式讓演算法獨立於使用它的客戶而變化,客戶端可以根據需要,很快切換這些演算法,並且保持可擴充套件性。
策略模式的本質:分離演算法,選擇實現。
2.UML類圖

策略模式結構中包括三種角色:
  • 策略(Strategy):策略是一個介面,該介面定義若干個演算法標識,即定義了若干個抽象方法。
  • 具體策略(ConcreteStrategy):具體策略是實現策略介面的類,具體策略實現策略介面所定義的抽象方法,即給出演算法標識的具體演算法。
  • 上下文(Context):上下文是依賴於策略介面的類,即上下文包含有策略宣告的變數。上下文中提供一個方法,該方法委託策略變數呼叫具體策略所實現的策略介面中的方法。
3.舉例說明
就拿程式設計師舉例吧,假如現在我要寫一個程式,我們可以使用java、php。那到底使用哪種語言呢?使用我們的簡單工廠模式完全可以解決這個問題
1、抽象語言類
public interface CodeLanguage {
    void useLanguage();
}
2、具體語言類
public class CodeJava implements CodeLanguage {
    @Override
    public void useLanguage() {
        System.out.println("使用Java語言程式設計");
    }
}
public class CodePhp implements CodeLanguage {
    @Override
    public void useLanguage() {
        System.out.println("使用Php語言程式設計");
    }
}
3、工廠類
public class LanguageFactory {
    public static CodeLanguage getLanguage(String type){
        CodeLanguage codeLanguage=null;
        switch (type){
            case "java":
                codeLanguage=new CodeJava();
                break;
            case "php":
                codeLanguage=new CodePhp();
                break;
        }
        return codeLanguage;
    }
}
雖然問題解決了,但正如我們所說的簡單工廠模式的缺點一樣:
  • 工廠類負責所有物件的建立邏輯,該類出問題整個系統無法執行。
  • 系統擴充套件困難,一旦新增新產品就不得不修改工廠方法。
  • 由於使用了靜態工廠方法,所以工廠角色無法形成基於繼承的等級結構。

    假如我們又要增加使用python語言,那麼我們除了增加相應的語言類,還要修改工廠類,很明顯這不是最佳的方法,而策略模式便是最好的選擇。根據策略模式的定義,策略模式主要是管理一堆有共性的演算法,這些演算法封裝到一個個的具體演算法類中,而這些具體演算法類都是一個抽象演算法類的子類。換言之,這些具體演算法類均有統一的介面,根據“里氏代換原則”和麵向物件的多型性,客戶端可以選擇使用任何一個具體演算法類,並只需要維持一個資料型別是抽象演算法類的物件。對比簡單工廠模式,我們發現,簡單工廠模式是利用工廠類根據引數來動態的生成具體產品類,然後單獨的呼叫具體產品類的方法;而策略模式是由客戶端建立這些具體演算法類,然後交由上下文來呼叫這些具體演算法類中的方法。
策略類介面
public interface CodeLanguage {
    void useLanguage();
}
具體策略類
public class CodeJava implements CodeLanguage {
    @Override
    public void useLanguage() {
        System.out.println("使用Java語言程式設計");
    }
}
public class CodePhp implements CodeLanguage {
    @Override
    public void useLanguage() {
        System.out.println("使用Php語言程式設計");
    }
}
這點和簡單工廠模式是抽象產品和具體產品程式碼是一樣的,下面是不同點,實現一個上下文物件
public class CodeContext {
    CodeLanguage codeLanguage;

    public CodeContext1(CodeLanguage codeLanguage) {
        this.codeLanguage = codeLanguage;
    }

    void useLanguage(){
        codeLanguage.useLanguage();
    }
}
使用模式
public class Application {
    public static void main(String[] args) {
        CodeContext context;
        context = new CodeContext(new CodeJava());
        context.useLanguage();
        context = new CodeContext(new CodePhp());
        context.useLanguage();
    }
}
上面是一個最簡單的策略模式的實現方式,按照功能分為3個部分,定義策略抽象介面,然後根據具體演算法實現策略介面,最後需要定義一個上下文物件。這裡的上下文物件主要用來切換演算法,上下文物件裡面也是針對介面程式設計,具體演算法實現被封裝了。
4.策略模式的理解
上面實現的只是一種最簡單的策略模式的框架,實際應用的時候,我們可以針對不同情況修改上下文物件和具體的演算法實現。比如說,可以增加一個抽象類實現作為演算法模板。抽象類裡面我們可以封裝一些公共的方法。這樣實現具體的演算法的時候,每個演算法公共部分就被分離出來。
策略模式的目的是把具體的演算法抽離出來,把每個演算法獨立出來。形成一系列有共同作用的演算法組,然後這個演算法組裡面的演算法可以根據實際情況進行相互替換。
策略模式的中心不是如何實現這些演算法,而是如何組織和呼叫這些演算法。也就是把我們平時寫到一塊的演算法解耦出來,獨立成一個模組,增強程式的擴充套件性。
策略模式裡面的演算法通常需要資料執行,我們可以根據實際情況把資料放在不同地方。例如可以放在上下文類裡面,然後每個演算法都可以使用這些資料。或者對介面封裝一個抽象類,在抽象類裡面新增資料。這些可以根據實際的情況綜合考慮。設計模式裡面沒有一成不變的萬能模式,每種模式都有變化版本,需要根據實際的專案進行變通。
5.策略模式優缺點
策略模式的優點:
策略模式完全符合“開放-封閉原則”。
策略模式提供了管理相關演算法族的辦法。恰當使用繼承可以把公共的程式碼移到抽象策略類中,從而避免重複的程式碼。
策略模式提供了一種可以替換繼承關係的辦法。如果不使用策略模式,那麼使用演算法的環境類就可能會有一些子類,每一個子類提供一種不同的演算法。但是,這樣一來演算法的使用就和演算法本身混在一起,不符合“單一職責原則”,而且使用繼承無法實現演算法或行為在程式執行時的動態切換。
使用策略模式可以避免多重條件選擇語句。
更好的擴充套件性:在策略模式中擴充套件新的策略實現非常容易,只要增加新的策略實現類,然後在選擇使用策略的地方選擇使用這個新的策略實現就好了。
策略模式缺點:
客戶端必須知道所有的策略類,並自行決定使用哪一個策略類,而且這樣也暴露了策略的具體實現。
由於策略模式把每個具體的策略實現都單獨封裝成為類,如果備選的策略很多的話,那麼物件的數目就會相應增多。
策略模式將造成系統產生很多具體策略類,任何細小的變化都將導致系統要增加一個新的具體策略類。
策略模式的一系列演算法地位是平等的,是可以相互替換的,事實上構成了一個扁平的演算法結構,也就是在一個策略介面下,有多個平等的策略演算法,就相當於兄弟演算法。而且在執行時刻只有一個演算法被使用,這就限制了演算法使用的層級,使用的時候不能巢狀使用。
6.模式優化
考慮到策略模式在使用的時候會過多的暴露策略類,程式碼的耦合性過高,結合簡單工廠模式進行優化,對上下文類進行修改
public class CodeContext {
    CodeLanguage codeLanguage;

    public CodeContext(String type) {
        switch (type){
            case "java":
                CodeJava codeJava=new CodeJava();
                codeLanguage=codeJava;
                break;
                case "php":
                CodePhp codePhp=new CodePhp();
                codeLanguage=codePhp;
                break;
        }
    }

    void useLanguage(){
        codeLanguage.useLanguage();
    }
}
使用模式
CodeContext context = new CodeContext("java");
context.useLanguage();
context = new CodeContext("php");
context.useLanguage();
相比傳統策略模式,我們把直接建立具體上下文物件改通過引數判斷來生成,這樣暴露給客戶端的只有上下文物件,程式碼的耦合度更低,可以說這既是簡單工廠模式的升級,也是策略模式的升級。

參考:


相關文章