設計模式之策略模式和狀態模式(strategy pattern & state pattern)

alpha_panda發表於2019-07-03

本文來講解一下兩個結構比較相似的行為設計模式:策略模式和狀態模式。兩者單獨的理解和學習都是比較直觀簡單的,但是實際使用的時候卻並不好實踐,算是易學難用的設計模式吧。這也是把兩者放在一起介紹的原因,經過對比和例項介紹,相信應該會一些比較深刻的感知。最後在結合個人的體會簡單聊一下對這兩個模式的一些看法。

1. 模式概念

1.1 策略模式

執行時更改類的行為或演算法,從而達到修改其功能的目的;

使用場景: 一個系統需要動態地在幾種演算法中選擇一種,而這些演算法之間僅僅是他們的行為不同。 此外決策過程中過多的出現if else,也可以考慮使用該模式。

實現:將這些演算法封裝成可單獨執行的類,由使用者根據需要進行替換。

優點: 較為靈活,擴充套件性好,避免大量的if else結構。

缺點: 對外暴露了類所有的行為和演算法,行為過多導致策略類膨脹。

1.2 狀態模式

執行時類的行為由其狀態決定;

使用場景: 物件依賴裝填,行為隨狀態改變而改變的情景,或者存在大量的if else和分支結構等;

實現:將物件的狀態封裝成單個的類,每個狀態處理該狀態下的事務,並控制該狀態到其他狀態的轉移;

優點: 容易新加狀態,封裝了狀態轉移規則,每個狀態可以被複用和共享,避免大量的if else結構。

缺點: 該模式結構和實現相對複雜,狀態過多導致增加類和物件個數。同時由於由每個狀態控制向其他狀態的轉移,新加狀態必須要修改現有的部分狀態才能加入狀態機中生效。

1.3 相同點

兩者通過將行為和狀態拆分成一系列小的元件,由條件和狀態進行功能更替,這樣符合開閉原則,便於擴充套件。此外均可作為if else或者分支的替換方案;支援的最大行為和狀態均有限;

1.4 不同點

  • 策略模式中,類的功能是根據當前條件主動更改;
  • 狀態模式中,類的功能是被動由當前狀態更改;
  • 策略模式中每個行為或演算法之間沒有關聯;
  • 狀態模式中的狀態之間有關聯,並且狀態本身控制著狀態轉移;

2. 原理

兩種模式的結構非常相似,下面分別看一下兩種設計模式的UML類圖:

2.1 策略模式UML

描述:

Context:

使用了某種策略的類,其行為由其包含的具體的策略決定,該類能主動修改使用的策略從而改變其行為;

Strategy:

抽象策略類,用於定義所有支援的演算法公共介面;

ConcreteStrategy:

能夠被Context使用的具體的策略;

2.2 狀態模式UML

描述:

Context:

帶有某個狀態標記的類,其行為由其當前的狀態決定,類狀態的轉移由狀態來控制;

State:

抽象狀態類,用於定義Context所有狀態的公共介面;

ConcreteState:

Context類的某種具體狀態,包含了該狀態下處理的事務並控制向他狀態轉移;

3. 例項——策略模式

舉個壓縮軟體使用不同壓縮策略的例子。

 

抽象策略介面:Compression

public interface Compression {
    public void doCompression();
}

快速壓縮演算法:Rapid

public class Rapid implements Compression {
    @Override
    public void doCompression() {
        System.out.println("Use rapid compression strategy!");
    }
}

高效壓縮演算法:Efficient

public class Efficient implements Compression {
    @Override
    public void doCompression() {
        System.out.println("Use efficient compression strategy!");
    }
}

加密壓縮演算法:Encrypt

public class Encrypt implements Compression {
    @Override
    public void doCompression() {
        // TODO Auto-generated method stub
        System.out.println("Use encrypt compression strategy!");
    }
}

整合上面壓縮演算法的軟體:WinRAR

public class WinRAR {
    
    private Compression compression = null;
    
    public WinRAR(Compression compression) {
        this.compression = compression;
    }
    
    public void setStrategy(Compression compression) {
        this.compression = compression;
    }
    
    public void compression() {
        if (compression != null) {
            compression.doCompression();
        }
    }
}

演示:

public class Demo {
    public static void main(String[] args) {
        WinRAR winrar = new WinRAR(new Rapid());
        winrar.compression();
        winrar.setStrategy(new Efficient());
        winrar.compression();
        winrar.setStrategy(new Encrypt());
        winrar.compression();
    }
}

結果:

Use rapid compression strategy!
Use efficient compression strategy!
Use encrypt compression strategy!

這個例子看著很直觀,後面會給出一點分析和個人的理解。

4. 例項——狀態模式

我們通過自動洗衣機工作過程來描述一下狀態模式使用。

簡單起見,這裡我們僅僅考慮【開始】-> 【工作】-> 【結束】,這三個狀態。

下面先來看一下其UML的類圖:

抽象狀態介面:State

public interface State {
    public void doJob(Washing washing);
}

開始狀態:Start

public class Start implements State {
    @Override
    public void doJob(Washing washing) {
        System.out.println("Start Washing Clothes!");
        washing.setState(new Work());
        washing.request();
    }
}

工作狀態:Work

public class Work implements State{
    @Override
    public void doJob(Washing washing) {
        System.out.println("Working Now!");
        washing.setState(new End());
        washing.request();
    }
}

結束狀態:End

public class End implements State{
    @Override
    public void doJob(Washing washing) {
        System.out.println("All Finished!");
        washing.setState(null);
    }
}

洗衣機類:Washing

public class Washing {
    private State state = null;
    
    public void setState(State state) {
        this.state = state;
        if (state == null) {
            System.out.println("Current state: null!");
        }
        else {
            System.out.println("Current state: " + state.getClass().getName());
        }
    }
    
    public void request() {
        if (state != null) {
            state.doJob(this);
        }
    }
}

演示:

public class Demo {
    public static void main(String[] args) {
        Washing washing = new Washing();
        washing.setState(new Start());
        washing.request();
    }
}

結果:

Current state: state.Start
Start Washing Clothes!
Current state: state.Work
Working Now!
Current state: state.End
All Finished!
Current state: null!

washing中提供使用者使用的主要介面。初始時,使用者使用一個狀態來配置washing,然後便可對washing傳送指令,後續不在需要使用者直接於具體轉態打交道。每個狀態會自動控制向下一個狀態轉移,直到執行結束。

5. 總結

談一下個人對於策略設計模式和狀態模式的一些理解(不一定對,僅僅是一些思考):

5.1 策略模式:

a)頻繁使用if else 可能嚴重消耗效能

策略模式比較適用於,行為類經常在某一個模式下工作,而不是會根據隨機條件進行切換。

舉個例子,在APP開發過程中,某一功能會依賴於橫豎屏狀態,那麼我們是否需要在每一幀都是使用if else進行判斷當前是橫屏還是豎屏,然後進行下一步的處理?

顯然這會嚴重消耗效能,正確的做法是將橫豎屏處理拆分成兩個策略,每次螢幕切換的時候,主動的切一下使用的模式;

b) 並不是所有的if else 和 分支都可以使用策略模式來替代

對於上面的壓縮軟體的例子,使用者會選用一種模式,然後進行下面的工作,這個沒問題。

但是如果我們提供的是一個壓縮命令,該命令可以根據傳遞的引數,使用不同的壓縮方式,那麼使用if else就是必要的,因為我們不知道使用者會輸入什麼引數,使用什麼模式。

c) 策略模式沒有策略

策略模式的核心是將一系列的操作拆分成可若干可單獨重複使用的輪子,特定條件下直接選取其中一個使用,而不是傳遞條件,使用if else來進行條件判斷以執行相應的操作。

從這個角度來看,策略模式名不副實,其不僅沒有智慧,合理的根據當前條件進行決策,還需要使用者主動的選取一種策略進行執行。這樣做有好處,但同時其也變得更加沒有策略。

實際開發過程中,我們都希望對方提供的介面簡單好用,最好一個介面能搞定所有的問題,因為對於呼叫者而言,我並不關心你的實現,我只關心簡單使用這個介面完成我的需求。

根本原因在於其破壞了封裝性,暴露了具體的策略,這是其拆分元件便於擴充套件的同時帶來的一個不可迴避的問題,策略模式將決策由執行者提前到了呼叫者,程式碼靈活可擴充套件的同時帶來的是使用的不便。

如果說策略模式主要是為了避免大量的if else決策,那麼語言支援的話完全可以使用hashtable,分別以條件和函式物件作為key,value來直接根據條件選取對應的操作。對於大量分支尤其適用。

因此實際開發過程中需要根據自己的實際情況權衡利弊。

5.1 策略模式:

狀態模式的核心是將物件每一個狀態做的事情分別交給每一個單獨的狀態物件處理,並且由狀態自己控制向其他狀態的轉移;行為類僅向外提供方便使用者使用的介面;

對擴充套件狀態不是特別友好,需要修改其他狀態的轉移。其次其實現比較靈活,用不好容易出錯。

小結:

策略模式是通過 Context 本身的決策來主動更替使用的strategy物件達到改變行為的目的,狀態模式通過狀態轉移來被動的更改當前的State物件,狀態的改變發生在執行時。

策略模式提前封裝一組可以互相替代的演算法族,根據需要動態的選擇合適的一個來處理問題,而狀態模式處理不同狀態下, Context 物件行為不同的問題;

相關文章