折騰Java設計模式之狀態模式

Java菜分享發表於2019-04-04

 在狀態模式(State Pattern)中,類的行為是基於它的狀態改變的。這種型別的設計模式屬於行為型模式。在狀態模式中,我們建立表示各種狀態的物件和一個行為隨著狀態物件改變而改變的 context 物件。通俗點就是一個物件在內部狀態發生改變時改變它的行為。

介紹

意圖允許物件在內部狀態發生改變時改變它的行為,物件看起來好像修改了它的類。

主要解決物件的行為依賴於它的狀態(屬性),並且可以根據它的狀態改變而改變它的相關行為。

何時使用程式碼中包含大量與物件狀態有關的條件語句。

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

關鍵程式碼通常命令模式的介面中只有一個方法。而狀態模式的介面中有一個或者多個方法。而且,狀態模式的實現類的方法,一般返回值,或者是改變例項變數的值。也就是說,狀態模式一般和物件的狀態有關。實現類的方法有不同的功能,覆蓋介面中的方法。狀態模式和命令模式一樣,也可以用於消除 if...else 等條件選擇語句。

UML圖

折騰Java設計模式之狀態模式


主要角色

1)Context(環境類):環境類擁有各種不同狀態的物件,作為外部使用的介面,負責呼叫狀態類介面。
2)State(抽象狀態):抽象狀態既可以為抽象類,也可以直接定義成介面。主要用於定義狀態抽象方法,具體實現由子類負責。
3)ConcreteState(具體狀態類):具體狀態類為抽象狀態的實現者,不同的狀態類對應這不同的狀態,其內部實現也不相同。環境類中使用不同狀態的物件時,能實現不同的處理邏輯
複製程式碼

應用例項

1、打籃球的時候運動員可以有正常狀態、不正常狀態和超常狀態。
2、曾侯乙編鐘中,'鍾是抽象介面','鍾A'等是具體狀態,'曾侯乙編鐘'是具體環境(Context)。
複製程式碼

優點

1、封裝了轉換規則。
2、列舉可能的狀態,在列舉狀態之前需要確定狀態種類。
3、將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,只需要改變物件狀態即可改變物件的行為。
4、允許狀態轉換邏輯與狀態物件合成一體,而不是某一個巨大的條件語句塊。
5、可以讓多個環境物件共享一個狀態物件,從而減少系統中物件的個數。
複製程式碼

缺點

1、狀態模式的使用必然會增加系統類和物件的個數。
2、狀態模式的結構與實現都較為複雜,如果使用不當將導致程式結構和程式碼的混亂。
3、狀態模式對"開閉原則"的支援並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的原始碼,否則無法切換到新增狀態,而且修改某個狀態類的行為也需修改對應類的原始碼。
複製程式碼

使用場景

  • 行為隨狀態改變而改變的場景。
  • 條件、分支語句的代替者。

狀態模式和策略模式的對比

現在我們知道,狀態模式和策略模式的結構是相似的,但它們的意圖不同。讓我們重溫一下它們的主要不同之處:

  1. 策略模式封裝了一組相關演算法,它允許Client在執行時使用可互換的行為;狀態模式幫助一個類在不同的狀態顯示不同的行為。
  2. 狀態模式封裝了物件的狀態,而策略模式封裝演算法或策略。因為狀態是跟物件密切相關的,它不能被重用;而通過從Context中分離出策略或演算法,我們可以重用它們。
  3. 在狀態模式中,每個狀態通過持有Context的引用,來實現狀態轉移;但是每個策略都不持有Context的引用,它們只是被Context使用。
  4. 策略實現可以作為引數傳遞給使用它的物件,例如Collections.sort(),它的引數包含一個Comparator策略。另一方面,狀態是Context物件自己的一部分,隨著時間的推移,Context物件從一個狀態轉移到另一個狀態。
  5. 雖然它們都符合OCP原則,策略模式也符合SRP原則(單一職責原則),因為每個策略都封裝自己的演算法,且不依賴其他策略。一個策略的改變,並不會導致其他策略的變化。
  6. 另一個理論上的不同:策略模式定義了物件“怎麼做”的部分。例如,排序物件怎麼對資料排序。狀態模式定義了物件“是什麼”和“什麼時候做”的部分。例如,物件處於什麼狀態,什麼時候處在某個特定的狀態。
  7. 狀態模式中很好的定義了狀態轉移的次序;而策略模式並無此需要:Client可以自由的選擇任何策略。
  8. 一些常見的策略模式的例子是封裝演算法,例如排序演算法,加密演算法或者壓縮演算法。如果你看到你的程式碼需要使用不同型別的相關演算法,那麼考慮使用策略模式吧。而識別何時使用狀態模式是很簡單的:如果你需要管理狀態和狀態轉移,但不想使用大量巢狀的條件語句,那麼就是它了。
  9. 最後但最重要的一個不同之處是,策略的改變由Client完成;而狀態的改變,由Context或狀態自己。

專案例項

simple1包中主要是對風扇的開關狀態進行轉換,其實我們是把狀態放在狀態類中進行按照固定的邏輯轉換,但是這種模式其實他不符合開閉原則,為什麼了,因為一旦我們發生新增、修改或者刪除狀態的時候,就需要修改狀態類中的狀態轉換。

public class Application {
    public static void main(String[] args) {
        Context context = new Context(new CloseLevelState());
        context.right();
        context.right();
        context.right();
        context.left();
        context.right();
        context.right();
    }
}
複製程式碼


折騰Java設計模式之狀態模式


抽象狀態

public interface LevelState {
    /**
     * 左轉
     *
     * @param context
     */
    void left(Context context);
    /**
     * 右轉
     *
     * @param context
     */
    void right(Context context);
    /**
     * 當前檔位
     * @return
     */
    String info();
}

複製程式碼

具體檔位狀態,我只列了2個,其他的類似

@Slf4j
public class OneLevelState implements LevelState {
    @Override
    public void left(Context context) {
        LevelState levelState = new CloseLevelState();
        context.setLevelState(levelState);
        log.info("風扇左轉到{}", levelState.info());
    }
    @Override
    public void right(Context context) {
        LevelState levelState = new TwoLevelState();
        context.setLevelState(levelState);
        log.info("風扇右轉到{}", levelState.info());
    }
    @Override
    public String info() {
        return "1檔";
    }
}

@Slf4j
public class CloseLevelState implements LevelState {
    @Override
    public void left(Context context) {
        LevelState levelState = new ForeLevelState();
        context.setLevelState(levelState);
        log.info("風扇左轉到{}", levelState.info());
    }
    @Override
    public void right(Context context) {
        LevelState levelState = new OneLevelState();
        context.setLevelState(levelState);
        log.info("風扇右轉到{}", levelState.info());
    }
    @Override
    public String info() {
        return "0檔";
    }
}

複製程式碼

真正的開關也就是上下文

@Data
@AllArgsConstructor
public class Context {
    private LevelState levelState;
    public void left() {
        levelState.left(this);
    }
    public void right() {
        levelState.right(this);
    }
    public String info() {
        return levelState.info();
    }
}
複製程式碼

歡迎工作一到五年的Java工程師朋友們加入Java程式設計師開發: 721575865

群內提供免費的Java架構學習資料(裡面有高可用、高併發、高效能及分散式、Jvm效能調優、Spring原始碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!


相關文章