不會狀態機?不知道狀態模式?不會利用它來減少if else並解耦?來看看這篇文章!!!

引言

狀態模式大家可能初聽會很陌生,這種模式有什麼用?我就是個CRUD BOY,面對不同的狀態,我一個狀態一個狀態的判斷,if else、if else...... 不斷的來寫不同的邏輯它不香嗎?

! 但是作爲一個傑出的後浪代表,僅僅如此怎能滿足我對知識的慾望!

我們知道面向對象的設計模式有七大基本原則:

  • 開閉原則(Open Closed Principle,OCP)
  • 單一職責原則(Single Responsibility Principle, SRP)
  • 里氏代換原則(Liskov Substitution Principle,LSP)
  • 依賴倒轉原則(Dependency Inversion Principle,DIP)
  • 接口隔離原則(Interface Segregation Principle,ISP)
  • 合成/聚合複用原則(Composite/Aggregate Reuse Principle,CARP)
  • 最少知識原則(Least Knowledge Principle,LKP)或者迪米特法則(Law of Demeter,LOD)

簡單理解就是:

  • 開閉原則是總綱,它指導我們要對擴展開放,對修改關閉;
  • 單一職責原則指導我們實現類要職責單一;里氏替換原則指導我們不要破壞繼承體系;
  • 依賴倒置原則指導我們要面向接口編程;接口隔離原則指導我們在設計接口的時候要精簡單一;
  • 迪米特法則指導我們要降低耦合。

設計模式就是通過這七個原則,來指導我們如何做一個好的設計。但是設計模式不是一套“奇技淫巧”,它是一套方法論,一種高內聚、低耦合的設計思想。我們可以在此基礎上自由的發揮,甚至設計出自己的一套設計模式。

當然,學習設計模式或者是在工程中實踐設計模式,必須深入到某一個特定的業務場景中去,再結合對業務場景的理解和領域模型的建立,才能體會到設計模式思想的精髓。如果脫離具體的業務邏輯去學習或者使用設計模式,那是極其空洞的。

接下來我們將通過業務的實踐,來探討如何用狀態設計模式來減少if else,實現可重用、易維護的代碼。

狀態模式

不知道大家在業務中會不會經常遇到這種情況:

產品:開發哥哥來下,你看我這邊想加個中間流程,這個流程是要怎樣怎樣處理.......,還想區分加了這些操作後的用戶,其他不符合這個條件的用戶不要影響,能不能實現啊!

我:能啊,加個狀態就行啊!於是將原流程加了個狀態,當用戶處於這個狀態時會如何如何......,於是改完上線,過了幾天。

產品:開發哥哥再來下,你看我這邊想加個中間流程,這個流程是要怎樣怎樣處理.......,還想區分加了這些操作後的用戶,其他不符合這個條件的用戶不要影響,能不能實現啊!

我:能啊!內心OS: 咦,似曾相識燕歸來,這不是之前加過了一個嗎,還加啊!於是吭哧吭哧又給加上了。本想就結束了,但是過了幾天,又來問了!於是就不斷的if else、if else的來判斷裝個修改原流程!最終一次不小心,動了下之前狀態的代碼,悲劇發生了,生產環境報錯了!

這是每個開發小哥哥都會遇到的問題,隨着業務的不斷髮展,我們定義表的狀態會不斷的擴展,狀態之間的流轉也會越來越複雜,原來的一小塊if else代碼也會更加的多和雜,着實讓人看着摸不着頭腦。

那有沒有一種模式能讓這些業務解耦開,涉及事件的產生和隨之產生的影響(狀態的流轉)。可以先將事件和事件產生後的狀態變化綁定起來。不同事件產生的狀態流轉也是不同的,我們可以從全局的角度來進行配置。

有的! 當然是我們今天的主角-狀態模式了

定義

在狀態模式(State Pattern)中,類的行爲是基於它的狀態改變的。這種類型的設計模式屬於行爲型模式。

在狀態模式中,我們創建表示各種狀態的對象和一個行爲隨着狀態對象改變而改變的 context 對象。

意圖

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

主要解決

對象的行爲依賴於它的狀態(屬性),並且可以根據它的狀態改變而改變它的相關行爲。

何時使用

代碼中包含大量與對象狀態有關的條件語句。

如何解決

將各種具體的狀態類抽象出來。

關鍵代碼

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

優點

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

缺點

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

使用場景

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

實際使用代碼

說了一堆的概念,大家肯定還是模糊的,那麼來這個場景看看吧

場景

作爲一個小up,最大的願望就是自己寫的東西能被更多人看到了。投幣,點贊,收藏,一鍵三聯的操作大家應該熟悉吧,大家的熱情直接影響up的更新頻率,那麼此時事件和狀態就出現了:

  • 事件:投幣,點贊,收藏
  • 狀態:SOMETIME(想起來什麼時候更新就什麼時候更新),OFTEN(會經常更新下),USUALLY(有事也更新),ALWAYS(沒停過的肝)

我們可以得到一個關係:

  • 投幣:UpSometimeState -> UpOftenState
  • 點贊:UpOftenState -> UpUsuallyState
  • 收藏:UpUsuallyState -> UpAlwaysState
  • 英文頻率從低到高:Sometime -> Often -> Usually -> Always

瞭解基本信息後,我們來基於設計模式原則來面向對象開發吧!

代碼

我們先定義一個狀態的抽象類,用來表示up的更新頻率

package cn.guess.statemachine.one;

import lombok.Data;

/**
 * @program: guess
 * @description: up主更新頻率狀態接口
 * @author: xingcheng
 * @create: 2020-05-10 12:18
 **/
@Data
public abstract class UpState {

    /**
     * 當前up狀態下的上下文
     */
    protected BlogContext blogContext;

    /**
     * 該狀態下的操作
     */
    protected abstract void doAction();

    /**
     * 切換狀態
     */
    protected abstract void switchState();
}

接着我們定義子類,分別表示每個不同的狀態:

package cn.guess.statemachine.one;

/**
 * @program: guess
 * @description: up主Sometime更新狀態
 * @author: xingcheng
 * @create: 2020-05-10 12:22
 **/
public class UpSometimeState extends UpState {

    @Override
    public void doAction() {
        System.out.println("nowUpState: " + toString());
    }

    @Override
    protected void switchState() {
        System.out.println("originUpState: " + blogContext.getUpState().toString());
        // 切換狀態 動作:投幣   狀態流轉:UpSometimeState -> UpOftenState
        blogContext.setUpState(new UpOftenState());
        // 執行動作
        blogContext.getUpState().doAction();
    }

    @Override
    public String toString() {
        return "UpSometimeState";
    }
}

package cn.guess.statemachine.one;

import lombok.extern.slf4j.Slf4j;

/**
 * @program: guess
 * @description: up主Often更新狀態
 * @author: xingcheng
 * @create: 2020-05-10 12:22
 **/
@Slf4j
public class UpOftenState extends UpState {

    @Override
    public void doAction() {
        System.out.println("nowUpState: " + toString());
    }

    @Override
    protected void switchState() {
        System.out.println("originUpState: " + blogContext.getUpState().toString());
        // 切換狀態 動作:投幣   狀態流轉:UpOftenState -> UpUsuallyState
        blogContext.setUpState(BlogContext.UP_USUALLY_STATE);
        // 執行動作
        blogContext.getUpState().doAction();
    }

    @Override
    public String toString() {
        return "UpOftenState";
    }
}

package cn.guess.statemachine.one;

import lombok.extern.slf4j.Slf4j;

/**
 * @program: guess
 * @description: up主Usually更新狀態
 * @author: xingcheng
 * @create: 2020-05-10 12:22
 **/
@Slf4j
public class UpUsuallyState extends UpState {

    @Override
    public void doAction() {
        System.out.println("nowUpState: " + toString());
    }

    @Override
    protected void switchState() {
        System.out.println("originUpState: " + blogContext.getUpState().toString());
        // 切換狀態 動作:投幣   狀態流轉:UpUsuallyState -> UpAlwaysState
        blogContext.setUpState(BlogContext.UP_ALWAYS_STATE);
        // 執行動作
        blogContext.getUpState().doAction();
    }

    @Override
    public String toString() {
        return "UpUsuallyState";
    }
}

package cn.guess.statemachine.one;

import lombok.extern.slf4j.Slf4j;

/**
 * @program: guess
 * @description: up主Always更新狀態
 * @author: xingcheng
 * @create: 2020-05-10 12:22
 **/
@Slf4j
public class UpAlwaysState extends UpState {

    @Override
    public void doAction() {
        System.out.println("nowUpState: " + toString());
    }

    @Override
    protected void switchState() {
        System.out.println("originUpState: " + blogContext.getUpState().toString());
        // 終態,不切換狀態
        // 執行動作
        blogContext.getUpState().doAction();
    }

    @Override
    public String toString() {
        return "UpAlwaysState";
    }
}

我們還需要一個上下文環境來進行狀態的流轉關聯

package cn.guess.statemachine.one;

import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @program: guess
 * @description: 博客上下文相關信息包裝
 * 投幣:UpSometimeState -> UpOftenState
 * 點贊:UpOftenState -> UpUsuallyState
 * 收藏:UpUsuallyState -> UpAlwaysState
 * 英文頻率從低到高:Sometime -> Often -> Usually -> Always
 * @author: xingcheng
 * @create: 2020-05-10 12:17
 **/
@Data
@NoArgsConstructor
public class BlogContext {

    public final static UpSometimeState UP_SOMETIME_STATE = new UpSometimeState();
    
    public final static UpOftenState UP_OFTEN_STATE = new UpOftenState();
    
    public final static UpUsuallyState UP_USUALLY_STATE = new UpUsuallyState();
    
    public final static UpAlwaysState UP_ALWAYS_STATE = new UpAlwaysState();

    /**
     * 當前up主狀態
     */
    private UpState upState;

    public BlogContext(UpState upState) {
        this.upState = upState;
        this.upState.setBlogContext(this);
    }

    /**
     * 用戶對博客內容的動作-投幣
     */
    public static void throwCoin() {
        new BlogContext(BlogContext.UP_SOMETIME_STATE).getUpState().switchState();
    }

    /**
     * 用戶對博客內容的動作-點贊
     */
    public static void like() {
        new BlogContext(BlogContext.UP_OFTEN_STATE).getUpState().switchState();
    }

    /**
     * 用戶對博客內容的動作-收藏
     */
    public static void collect() {
        new BlogContext(BlogContext.UP_USUALLY_STATE).getUpState().switchState();
    }
}

接着我們寫一個客戶端來模擬調用流程:

package cn.guess.statemachine.one;

/**
 * @program: guess
 * @description: 狀態切換執行器
 * @author: xingcheng
 * @create: 2020-05-10 15:36
 **/
public class UpStateClient {

    public static void main(String[] args) {
        // 開始模擬每個動作事件-會自動進行狀態轉化
        // 投幣
        System.out.println("投幣動作");
        BlogContext.throwCoin();
        System.out.println("-----------------------------------------------------------------------");
        // 點贊
        System.out.println("點贊動作");
        BlogContext.like();
        System.out.println("-----------------------------------------------------------------------");
        // 收藏
        System.out.println("收藏動作");
        BlogContext.collect();
    }

}

此時,狀態模式便完成了,可以看到我們沒有用到if else,便完成了判斷。

每個狀態也是由一個類來代替的,我們對其中一個狀態進行的改動,不會影響其他的狀態邏輯

通過這樣的方式,很好的實現了對擴展開放,對修改關閉的原則。

我們看下輸出:

有的小朋友要問了,開發哥哥,我們現在開發環境幾乎都是springboot了,能不能結合spring這麼強大的生態,來實現這一模式呢?

能! 毋庸置疑,能結合spring強大的IOC和AOP,完全可以實現一個狀態自動機啊!!!

SpringBoot狀態自動機

還是剛剛的場景,我們通過Spring StateMachine來實現下。

代碼

包的引入:

<dependency>
	<groupId>org.springframework.statemachine</groupId>
	<artifactId>spring-statemachine-core</artifactId>
	<version>${spring-boot-statemachine.version}</version>
</dependency>

我這邊使用的是<spring-boot-statemachine.version>2.2.0.RELEASE</spring-boot-statemachine.version>版本

定義狀態和事件枚舉

package cn.guess.statemachine.tow.enums;

import java.util.Objects;

/**
 * up狀態事件枚舉 英文頻率從低到高:Sometime -> Often -> Usually -> Always
 * @program: guess
 * @author: xingcheng
 * @create: 2020-05-10 16:12
 **/
public enum UpStateEnum {

    UP_SOMETIME_STATE(0, "SOMETIME"),
    UP_OFTEN_STATE(10, "OFTEN"),
    UP_USUALLY_STATE(20, "USUALLY"),
    UP_ALWAYS_STATE(30, "ALWAYS"),
    ;

    /**
     * 枚舉編碼
     */
    private final int code;

    /**
     * 枚舉描述
     */
    private final String value;

    public int getCode() {
        return code;
    }

    public String getValue() {
        return value;
    }

    UpStateEnum(int code, String value) {
        this.code = code;
        this.value = value;
    }

    /**
     * 根據枚舉key值轉化爲枚舉對象
     *
     * @param key 枚舉值
     * @return 枚舉對象
     */
    public static UpStateEnum keyOf(int key) {
        UpStateEnum[] values = values();
        for (UpStateEnum stateEnum : values) {
            if (Objects.equals(stateEnum.getCode(), key)) {
                return stateEnum;
            }
        }

        return null;
    }

}

package cn.guess.statemachine.tow.enums;

import java.util.Objects;

/**
 * 博客事件枚舉
 * @program: guess
 * @author: xingcheng
 * @create: 2020-05-10 16:08
 **/
public enum BlobEventEnum {

    THROW_COIN(0, "投幣"),
    LIKE(10, "點贊"),
    COLLECT(20, "收藏"),
    ;

    /**
     * 枚舉編碼
     */
    private final int code;

    /**
     * 枚舉描述
     */
    private final String value;

    public int getCode() {
        return code;
    }

    public String getValue() {
        return value;
    }

    BlobEventEnum(int code, String value) {
        this.code = code;
        this.value = value;
    }

    /**
     * 根據枚舉key值轉化爲枚舉對象
     *
     * @param key 枚舉值
     * @return 枚舉對象
     */
    public static BlobEventEnum keyOf(int key) {
        BlobEventEnum[] values = values();
        for (BlobEventEnum stateEnum : values) {
            if (Objects.equals(stateEnum.getCode(), key)) {
                return stateEnum;
            }
        }

        return null;
    }

}

創建狀態機配置類

package cn.guess.statemachine.tow.config;

import cn.guess.statemachine.tow.enums.BlobEventEnum;
import cn.guess.statemachine.tow.enums.UpStateEnum;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.transition.Transition;

import java.util.EnumSet;

/**
 * @program: guess
 * @description: 該註解用來啓用Spring StateMachine狀態機功能
 * @author: xingcheng
 * @create: 2020-05-10 16:14
 **/
@EnableStateMachine
@Configuration
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<UpStateEnum, BlobEventEnum> {

    /**
     * configure用來初始化當前狀態機擁有哪些狀態
     *
     * @param states
     * @throws Exception
     */
    @Override
    public void configure(StateMachineStateConfigurer<UpStateEnum, BlobEventEnum> states) throws Exception {
        states
                .withStates()
                // 定義了初始狀態爲UP_SOMETIME_STATE
                .initial(UpStateEnum.UP_SOMETIME_STATE)
                //指定UpStateEnum中的所有狀態作爲該狀態機的狀態定義
                .states(EnumSet.allOf(UpStateEnum.class));
    }

    /**
     * configure用來初始化當前狀態機有哪些狀態遷移動作
     * 從其中命名中我們很容易理解每一個遷移動作,都有來源狀態source,目標狀態target以及觸發事件event
     * 事件和狀態流轉關係綁定:類似BlogContext的throwCoin及UpSometimeState下的switchState的過程
     *
     * @param transitions
     * @throws Exception
     */
    @Override
    public void configure(StateMachineTransitionConfigurer<UpStateEnum, BlobEventEnum> transitions) throws Exception {
        transitions
                .withExternal()
                // 投幣:UpSometimeState -> UpOftenState
                .source(UpStateEnum.UP_SOMETIME_STATE).target(UpStateEnum.UP_OFTEN_STATE)
                .event(BlobEventEnum.THROW_COIN)
                .and()
                .withExternal()
                // 點贊:UpOftenState -> UpUsuallyState
                .source(UpStateEnum.UP_OFTEN_STATE).target(UpStateEnum.UP_USUALLY_STATE)
                .event(BlobEventEnum.LIKE)
                .and()
                .withExternal()
                // 收藏:UpUsuallyState -> UpAlwaysState
                .source(UpStateEnum.UP_USUALLY_STATE).target(UpStateEnum.UP_ALWAYS_STATE)
                .event(BlobEventEnum.COLLECT);
    }

    /**
     * configure爲當前的狀態機指定了狀態監聽器,其中listener()則是調用了下一個函數創建的監聽器實例,用來處理各個各個發生的狀態遷移事件。
     * 這裏註釋是因爲我們有其他更好的方法去替代
     */
//    @Override
//    public void configure(StateMachineConfigurationConfigurer<UpStateEnum, BlobEventEnum> config) throws Exception {
//        config
//                .withConfiguration()
//                // 指定狀態機的處理監聽器
//                .listener(listener());
//    }

    /**
     * listener()方法用來創建StateMachineListener狀態監聽器的實例,
     * 在該實例中會定義具體的狀態遷移處理邏輯,上面的實現中只是做了一些輸出,
     * 實際業務場景會有更嚴密的邏輯,所以通常情況下,我們可以將該實例的定義放到獨立的類定義中,並用注入的方式加載進來。
     * 這裏註釋是因爲我們有其他更好的方法去替代
     */
//    @Bean
//    public StateMachineListener<UpStateEnum, BlobEventEnum> listener() {
//        return new StateMachineListenerAdapter<UpStateEnum, BlobEventEnum>() {
//
//            @Override
//            public void transition(Transition<UpStateEnum, BlobEventEnum> transition) {
//                if (transition.getTarget().getId() == UpStateEnum.UP_SOMETIME_STATE) {
//                    System.out.println("up sometime update blob");
//                    return;
//                }
//
//                if (transition.getSource().getId() == UpStateEnum.UP_SOMETIME_STATE
//                        && transition.getTarget().getId() == UpStateEnum.UP_OFTEN_STATE) {
//                    System.out.println("user throw coin, up sometime update blob");
//                    return;
//                }
//
//                if (transition.getSource().getId() == UpStateEnum.UP_OFTEN_STATE
//                        && transition.getTarget().getId() == UpStateEnum.UP_USUALLY_STATE) {
//                    System.out.println("user like blob, up usually update blob");
//                    return;
//                }
//
//                if (transition.getSource().getId() == UpStateEnum.UP_USUALLY_STATE
//                        && transition.getTarget().getId() == UpStateEnum.UP_ALWAYS_STATE) {
//                    System.out.println("user collect blob, up always update blob");
//                    return;
//                }
//
//                if (transition.getSource().getId() == UpStateEnum.UP_ALWAYS_STATE) {
//                    System.out.println("up always update blob");
//                    return;
//                }
//            }
//
//        };
//    }
}

註解監聽器

package cn.guess.statemachine.tow.config;

import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.OnTransitionEnd;
import org.springframework.statemachine.annotation.OnTransitionStart;
import org.springframework.statemachine.annotation.WithStateMachine;

/**
 * @program: guess
 * @description: 該配置實現了cn.guess.statemachine.tow.config.StateMachineConfig類中定義的狀態機監聽器實現
 * @author: xingcheng
 * @create: 2020-05-10 16:31
 **/
@WithStateMachine
public class EventConfig {

    @OnTransition(target = "UP_SOMETIME_STATE")
    public void initState() {
        System.out.println("up sometime update blob");
    }

    @OnTransition(source = "UP_SOMETIME_STATE", target = "UP_OFTEN_STATE")
    public void throwCoin() {
        System.out.println("up sometime update blob");
    }

    @OnTransitionStart(source = "UP_SOMETIME_STATE", target = "UP_OFTEN_STATE")
    public void throwCoinStart() {
        System.out.println("up sometime update blob start");
    }

    @OnTransitionEnd(source = "UP_SOMETIME_STATE", target = "UP_OFTEN_STATE")
    public void throwCoinEnd() {
        System.out.println("up sometime update blob end");
    }

    @OnTransition(source = "UP_OFTEN_STATE", target = "UP_USUALLY_STATE")
    public void like() {
        System.out.println("user like blob, up usually update blob");
    }

    @OnTransition(source = "UP_USUALLY_STATE", target = "UP_ALWAYS_STATE")
    public void collect() {
        System.out.println("user collect blob, up always update blob");
    }

}

創建應用Controller來完成流程

package cn.guess.statemachine.tow.controller;

import cn.guess.common.api.ApiResult;
import cn.guess.common.web.controller.BaseController;
import cn.guess.statemachine.tow.enums.BlobEventEnum;
import cn.guess.statemachine.tow.enums.UpStateEnum;
import cn.guess.system.web.res.UserSelfCenterInfoRes;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: guess
 * @description: state檢測相關接口
 * @author: xingcheng
 * @create: 2020-05-10 16:38
 **/
@Slf4j
@RestController
@RequestMapping("/api/state")
@Api(value = "state檢測相關接口 API", description = "03.state檢測相關接口")
public class StateController extends BaseController {

    @Autowired
    private StateMachine<UpStateEnum, BlobEventEnum> stateMachine;

    @GetMapping("/v1/run")
    @ApiOperation(value = "state檢測請求")
    public String stateRun() {
        // start()就是創建這個up主的發博客流程,根據之前的定義,該up會處於不經常更新(SOMETIME)狀態
        stateMachine.start();
        // 通過調用sendEvent(Events.THROW_COIN)執行投幣操作
        stateMachine.sendEvent(BlobEventEnum.THROW_COIN);
        // 通過調用sendEvent(Events.THROW_COIN)執行點贊操作
        stateMachine.sendEvent(BlobEventEnum.LIKE);
        // 通過調用sendEvent(Events.THROW_COIN)執行收藏操作
        stateMachine.sendEvent(BlobEventEnum.COLLECT);
        return "OK";
    }

}

調用結果

說明

我們可以對如何使用Spring StateMachine做如下小結:

  • 定義狀態和事件枚舉

  • 爲狀態機定義使用的所有狀態以及初始狀態

  • 爲狀態機定義狀態的遷移動作

  • 爲狀態機指定監聽處理器

狀態監聽器

通過上面的入門示例以及最後的小結,我們可以看到使用Spring StateMachine來實現狀態機的時候,代碼邏輯變得非常簡單並且具有層次化。

整個狀態的調度邏輯主要依靠配置方式的定義,而所有的業務邏輯操作都被定義在了狀態監聽器中。

其實狀態監聽器可以實現的功能遠不止上面我們所述的內容,它還有更多的事件捕獲,我們可以通過查看StateMachineListener接口來了解它所有的事件定義:

總結

狀態模式的核心是封裝,將狀態以及狀態轉換邏輯封裝到類的內部來實現,也很好的體現了“開閉原則”和“單一職責原則”。每一個狀態都是一個子類,不管是修改還是增加狀態,只需要修改或者增加一個子類即可。在我們的應用場景中,狀態數量以及狀態轉換遠比上述例子複雜,通過“狀態模式”避免了大量的if-else代碼,讓我們的邏輯變得更加清晰。同時由於狀態模式的良好的封裝性以及遵循的設計原則,讓我們在複雜的業務場景中,能夠遊刃有餘地管理各個狀態。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章