孿生兄弟狀態模式與策略模式有什麼區別,究竟該如何選擇

都說狀態模式和策略模式很像,它們的 UML 類圖同樣。這也說明,單純從代碼角度來說,它們的本質同樣,其實都是多態的應用。但它們實際所表明的的事物特徵是有本質區別的,選擇哪一個設計模式,表明了你看待業務場景的角度。從合理角度地對業務進程抽象,選擇恰當的設計模式,才能讓代碼有更好的結構。
這篇文章重點說說我對狀態模式和策略模式區別的理解,以及如何選擇。html

1、策略模式

關於策略模式,我以前寫過一篇筆記,不過是 C# 寫的。策略模式解決了代碼邏輯分支較多,對不一樣的分支,採起不一樣措施的問題。不熟悉策略模式的,也能夠上集回顧: 扯一扯 C#委託和事件?策略模式?接口回調?算法

策略模式簡介

在策略模式(Strategy Pattern)中,一個類的行爲或其算法能夠在運行時更改。咱們建立表示各類策略的對象和一個行爲隨着策略對象改變而改變的 context 對象。策略對象改變 context 對象的執行算法。這種類型的設計模式屬於行爲型模式。設計模式

意圖:定義一系列的算法,把它們一個個封裝起來, 而且使它們可相互替換。
主要解決:在有多種算法類似的狀況下,使用 if...else 所帶來的複雜和難以維護。
什麼時候使用:一個系統有許多許多類,而區分它們的只是他們直接的行爲。
如何解決:將這些算法封裝成一個一個的類,任意地替換。
關鍵代碼:實現同一個接口。安全

策略模式的模型代碼

  • 策略的抽象,定一個策略接口,聲明不一樣策略方案所需實現的方法:
public interface Stragety {
    void function();
}
  • 具體的策略類,定義不一樣的策略類,實現策略抽象接口:
public class StrategyA implements Stragety {

    @Override
    public void function() {
        System.out.println("invoke StrategyA function ...");
    }
}
public class StrategyB implements Stragety {
    @Override
    public void function() {
        System.out.println("invoke StrategyB function ...");
    }
}
  • 操做策略的上下文環境,Context 類:
public class Context {
    private Stragety stragety;

    public Context() {
    }

    public Context(Stragety stragety) {
        this.stragety = stragety;
    }

    public void setStragety(Stragety stragety) {
        this.stragety = stragety;
    }

    public void function() {
        if (stragety == null) {
            System.out.println("not set strategy...");
            return;
        }
        stragety.function();
    }
}

最後調用的測試代碼以下:ide

public class Test {
    public static void main(String[] args) {
        Context context = new Context();
        context.function();
        context.setStragety(new StrategyA());
        context.function();
        context.setStragety(new StrategyB());
        context.function();
    }
}

結果以下:工具

2、狀態模式

狀態模式中的行爲是由狀態來決定的,在狀態模式(State Pattern)中,類的行爲是基於它的狀態改變的。咱們建立表示各類狀態的對象和一個行爲隨着狀態對象改變而改變的 context 對象。這種類型的設計模式也屬於行爲型模式。測試

狀態模式簡介

意圖:容許對象在內部狀態發生改變時改變它的行爲,對象看起來好像修改了它的類。this

主要解決:對象的行爲依賴於它的狀態(屬性),而且能夠根據它的狀態改變而改變它的相關行爲。設計

什麼時候使用:代碼中包含大量與對象狀態有關的條件語句。code

如何解決:將各類具體的狀態類抽象出來。

關鍵代碼:狀態模式的接口中一般有一個或者多個方法。並且,狀態模式的實現類的方法,通常返回值,或者是改變實例變量的值。也就是說,狀態模式通常和對象的狀態有關。實現類的方法有不一樣的功能,覆蓋接口中的方法。狀態模式和策略模式同樣,也能夠用於消除 if...else 等條件選擇語句。

狀態模式的模型代碼

  • 狀態的抽象,定一個狀態接口,聲明不一樣狀態下所需實現的方法:
public interface State {
    void function1();

    void function2();
}
  • 具體的狀態類,定義不一樣的狀態類,實現狀態抽象接口:
public class StateA implements State {
    @Override
    public void function1() {
        System.out.println("invoke StateA function1 ...");
    }

    @Override
    public void function2() {
        System.out.println("invoke StateA function2 ...");
    }
}
public class StateB implements State {
    @Override
    public void function1() {
        System.out.println("invoke StateB function1 ...");
    }

    @Override
    public void function2() {
        System.out.println("invoke StateB function2 ...");
    }
}
  • 維護狀態的上下文環境,Context 類:
public class Context {
    private State state;

    public Context() {
    }

    public Context(State originalState) {
        this.state = originalState;
    }

    public void setState(State state) {
        this.state = state;
    }

    public void setStateA() {
        setState(new StateA());
    }

    public void setStateB() {
        setState(new StateB());
    }

    public void function1() {
        state.function1();
    }

    public void function2() {
        state.function2();
    }
}

最後調用的測試代碼以下:

public class Test {
    public static void main(String[] args) {
        Context context = new Context();
        context.setStateA();
        context.function1();
        context.function2();
        context.setStateB();
        context.function1();
        context.function2();
    }
}

結果以下:

3、狀態模式和策略模式的區別

經過上面模型代碼的對比,有的同窗可能發現了,乍一看代碼,其實二者幾乎沒有沒什麼區別,都是在玩多態的語法糖。這讓我想起了經典書籍《重構,改善既有代碼的設計》第一章中的那個例子,重構篇中有以下一段話——

能夠用多態來取代switch語句,可是由於:一部影片能夠在生命週期內修改本身的分類,一個對象卻不能在生命週期內修改本身所屬的類。因此這裏不能用策略模式,用多態取代switch,而應該用狀態模式(State)。       
                                 
這是一個State模式仍是一個Strategy模式?答案取決於Price類究竟表明計費方式,仍是表明影片的某個狀態。

也就是說對於一個場景的抽象選擇策略模式仍是狀態模式,取決於你對這個場景的認知。個人理解是策略,即方法,不一樣的策略實現類中相同的方法,地位應該是平等的。舉幾個例子,早餐選擇吃麪包,中餐選擇吃米飯,這是策略上的決定;交通工具是選擇乘坐公交車仍是乘坐地鐵;在中國選擇說中文,在美國選擇說英語...

而狀態之間,每每伴隨着轉換,即狀態遷移,多是在時序上的狀態遷移,也多是在某個狀態上執行某種行爲(調用某個方法)的時候,轉化爲另一種狀態。它的重點應該是狀態遷移,譬如燒水過程當中,水溫能夠當作狀態;手機移動數據藍牙WiFi的開關、汽車行駛的速度;在中國的時候說中文,在美國的時候說英語...均可以抽象成狀態。咦,等等!!!在中國選擇說中文,在美國選擇說英語,到底抽象成策略模式仍是狀態模式?

其實這就仍是要看你是怎麼看待這個場景了,你要把它當作兩中平等的場景,只是在不一樣的場景中,作一個選擇,則能夠抽象成策略模式,若是你把在中國和在美國當作兩種狀態,而且兩種狀態能夠發生轉換,好比處於在中國這種狀態下,有一個搭乘飛機飛到了中國的行爲,狀態變成了在中國,此時就應該考慮抽象成狀態模式。

  1. 狀態轉換場景中,最好不要由客戶端來直接改變狀態(也不是絕對不能夠),而是客戶端作了某種其它操做引發狀態遷移,也就是說客戶端最好不要直接建立一個具體 State 實現類的實例對象,經過 setState() 方法來設置。
  2. 狀態轉換也多是對象的內部行爲形成的。

這麼一想,狀態模式和策略模式代碼實現仍是有區別的,下面我結合以上想法將狀態模式模型代碼作修改。
針對第一點,好比,客戶端執行了 actionB 方法方法,使得狀態改變成 StateB , 針對第二點,假設,在狀態 StateB 中,執行 function2 的時候,會切換狀態爲 StateA。此時代碼應該是這樣的:

  • 定一個狀態接口:
public interface State {
    void function1();

    void function2(Context context);
}
  • 具體的狀態類:
public class StateA implements State {
    @Override
    public void function1() {
        System.out.println("invoke StateA function1 ...");
    }

    @Override
    public void function2(Context context) {
        System.out.println("invoke StateA function2 ...");
    }
}
public class StateB implements State {
    @Override
    public void function1() {
        System.out.println("invoke StateB function1 ...");
    }

    @Override
    public void function2(Context context) {
        System.out.println("invoke StateB function2 ...");
        context.setStateA();
    }
}
  • 增長一個可能在其它操做中改變狀態的方法,封裝成接口:
public interface Others {
    void actionB();
}
  • 維護狀態的上下文環境,Context 類:
public class Context implements Others{
    private State state;

    public Context() {
    }

    // 爲了方便下文說明,標記爲--------MARK1
    public Context(State originalState) {
        this.state = originalState;
    }

    // 爲了方便下文說明,標記爲--------MARK2
    public void setState(State state) {
        this.state = state;
    }

    public void setStateA() {
        setState(new StateA());
    }

    public void setStateB() {
        setState(new StateB());
    }

    public void function1() {
        state.function1();
    }

    public void function2() {
        state.function2(this);
    }

    @Override
    public void actionB() {
        System.out.println("invoke actionB ...");
        setStateB();
    }
}

最後調用的測試代碼以下:

public class Test {
    public static void main(String[] args) {
        Context context = new Context();
        context.setStateA();
        context.function1();
        context.actionB();
        context.function1();
        context.function2();
        context.function1();
        context.function2();
    }
}

此時的執行結果以下:

4、總結

在現實世界中,策略和狀態是兩種徹底不一樣的思想。雖然狀態模式和策略模式擁有類似的結構,雖然它們都基於開閉原則,可是,它們的意圖是徹底不一樣的。當咱們對狀態和策略進行建模時,這種差別會致使徹底不一樣的問題。對狀態進行建模時,狀態遷移是一個核心內容,狀態模式幫助對象管理狀態;而在選擇策略時,狀態遷移與此毫無關係。另外,策略模式容許一個客戶選擇或提供一種策略,而這種思想在狀態模式中徹底沒有,因此在狀態模式中,若是須要避免客戶端的不安全操做,咱們徹底能夠不提供代碼中標記爲 MARK1 的構造器,並將代碼中標記爲 MARK2 的方法私有化。 從代碼上理解:是誰促使了行爲的改變。狀態模式中,狀態轉移由 Context 或 State 本身管理。若是你在State中管理狀態轉移,那麼它必須持有Context的引用。例如,在上面代碼中,StateB 的 function2() 方法須要調用 setState()方法去改變它的狀態,它就須要傳入一個 Context 類型參數。而策略模式中,Strategy 從不持有Context的引用,是客戶端把所選擇的 Strategy 傳遞給Context。因爲狀態模式和策略模式在工做中的使用場景比較多(我本身最近項目就有用到),因此本文重點分析記錄狀態模式和策略模式的異同,來加深我本身對它們的理解。也但願能幫助到有緣看到本文的朋友。

相關文章
相關標籤/搜索