服務端指南 | 狀態機設計

原文地址:服務端指南 | 狀態機設計
博客地址:blog.720ui.com/spring

狀態機中,每一個狀態有着相應的行爲,隨着行爲的觸發來切換狀態。其中一種作法是使用二維數組實現狀態機機制,其中橫座標表示行爲,縱座標表示狀態,具體的數值則表示當前的狀態。數組

咱們以登陸場景設計一個狀態機。bash

這時,咱們設計一張狀態機表。微信

那麼,此時它的二維數組,以下所示。ide

此外,咱們也能夠經過狀態模式實現一個狀態機。狀態模式將每個狀態封裝成獨立的類,具體行爲會隨着內部狀態而改變。狀態模式用類表示狀態,這樣咱們就能經過切換類來方便地改變對象的狀態,避免了冗長的條件分支語句,讓系統具備更好的靈活性和可擴展性。ui

如今,咱們定義一個狀態枚舉,其中包括未鏈接、已鏈接、註冊中、已註冊 4 種狀態。this

public enum StateEnum {
    // 未鏈接
    UNCONNECT(1, "UNCONNECT"), 
    // 已鏈接
    CONNECT(2, "CONNECT"), 
    // 註冊中
    REGISTING(3, "REGISTING"), 
    // 已註冊
    REGISTED(4, "REGISTED");

    private int key;
    private String value;

    StateEnum(int key, String value) {
        this.key = key;
        this.value = value;
    }
    public int getKey() {return key;}
    public String getValue() {return value;}
}複製代碼

定義一個環境類,它是其實是真正擁有狀態的對象。spa

public class Context {
    private State state;
    public void connect(){
        state.connect(this);
        System.out.println("STATE : " + state.getCurState());
    } 
    public void register(){
        state.register(this);
        System.out.println("STATE : " + state.getCurState());
    }   
    public void registerSuccess(){
        state.registerSuccess(this);
        System.out.println("STATE : " + state.getCurState());
    }
    public void registerFailed(){
        state.registerFailed(this);
        System.out.println("STATE : " + state.getCurState());
    }
    public void unRegister(){
        state.unRegister(this);
        System.out.println("STATE : " + state.getCurState());
    }
    public State getState() {
        return state;
    }
    public void setState(State state) {
        this.state = state;
    }
}複製代碼

狀態模式用類表示狀態,這樣咱們就能經過切換類來方便地改變對象的狀態。如今,咱們定義幾個狀態類。設計

public interface State {
    void connect(Context c);
    void register(Context c);
    void registerSuccess(Context c);
    void registerFailed(Context c);
    void unRegister(Context c);
    String getCurState();
}

public class UnconnectState implements State {
    @Override
    public void connect(Context c) {
        c.setState(new ConnectState());
    }
    @Override
    public void register(Context c) {
        throw new RuntimeException("INVALID_OPERATE_ERROR");
    }
    @Override
    public void registerSuccess(Context c) {
        throw new RuntimeException("INVALID_OPERATE_ERROR");
    }
    @Override
    public void registerFailed(Context c) {
        throw new RuntimeException("INVALID_OPERATE_ERROR");
    }
    @Override
    public void unRegister(Context c) {
        throw new RuntimeException("INVALID_OPERATE_ERROR");
    }
    @Override
    public String getCurState() {
        return StateEnum.UNCONNECT.toString();
    }
}

public class ConnectState implements State {
    @Override
    public void connect(Context c) {
        c.setState(new ConnectState());
    }
    @Override
    public void register(Context c) {
        c.setState(new RegistingState());
    }
    @Override
    public void registerSuccess(Context c) {
        throw new RuntimeException("INVALID_OPERATE_ERROR");
    }
    @Override
    public void registerFailed(Context c) {
        throw new RuntimeException("INVALID_OPERATE_ERROR");
    }
    @Override
    public void unRegister(Context c) {
        c.setState(new UnconnectState());
    }
    @Override
    public String getCurState() {
        return StateEnum.CONNECT.toString();
    }
}

public class RegistingState implements State {
    @Override
    public void connect(Context c) {
        throw new RuntimeException("INVALID_OPERATE_ERROR");
    }
    @Override
    public void register(Context c) {
        c.setState(new RegistingState());
    }
    @Override
    public void registerSuccess(Context c) {
        c.setState(new RegistedState());
    }
    @Override
    public void registerFailed(Context c) {
        c.setState(new UnconnectState());
    }
    @Override
    public void unRegister(Context c) {
        c.setState(new UnconnectState());
    }
    @Override
    public String getCurState() {
        return StateEnum.REGISTING.toString();
    }
}

public class RegistedState implements State {
    @Override
    public void connect(Context c) {
        throw new RuntimeException("INVALID_OPERATE_ERROR");
    }
    @Override
    public void register(Context c) {
        throw new RuntimeException("INVALID_OPERATE_ERROR");
    }
    @Override
    public void registerSuccess(Context c) {
        c.setState(new RegistedState());
    }
    @Override
    public void registerFailed(Context c) {
        throw new RuntimeException("INVALID_OPERATE_ERROR");
    }
    @Override
    public void unRegister(Context c) {
        c.setState(new UnconnectState());
    }
    @Override
    public String getCurState() {
        return StateEnum.REGISTED.toString();
    }
}複製代碼

注意的是,若是某個行爲不會觸發狀態的變化,咱們能夠拋出一個 RuntimeException 異常。此外,調用時,經過環境類控制狀態的切換,以下所示。code

public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        context.connect();
        context.register();
    }
}複製代碼

Spring StateMachine 讓狀態機結構更加層次化,能夠幫助開發者簡化狀態機的開發過程。如今,咱們來用 Spring StateMachine 進行改造。修改 pom 文件,添加 Maven 依賴。

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>1.2.0.RELEASE</version>
</dependency>複製代碼

定義一個狀態枚舉,其中包括未鏈接、已鏈接、註冊中、已註冊 4 種狀態。

public enum RegStatusEnum {
    // 未鏈接
    UNCONNECTED,
    // 已鏈接
    CONNECTED,
    // 註冊中
    REGISTERING,
    // 已註冊
    REGISTERED;
}複製代碼

定義一個行爲枚舉,其中包括鏈接、註冊、註冊成功、註冊失敗、註銷 5 種行爲事件。

public enum RegEventEnum {
    // 鏈接
    CONNECT,
    // 註冊
    REGISTER,
    // 註冊成功
    REGISTER_SUCCESS,
    // 註冊失敗
    REGISTER_FAILED,
    // 註銷
    UN_REGISTER;
}複製代碼

接着,咱們須要進行狀態機配置,其中 @EnableStateMachine 註解,標識啓用 Spring StateMachine 狀態機功能。

@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<RegStatusEnum, RegEventEnum> {
}複製代碼

咱們須要初始化狀態機的狀態。其中,initial(RegStatusEnum.UNCONNECTED) 定義了初始狀態是未鏈接狀態。states(EnumSet.allOf(RegStatusEnum.class)) 定義了狀態機中存在的全部狀態。

@Override
public void configure(StateMachineStateConfigurer<RegStatusEnum, RegEventEnum> states) throws Exception {
    states.withStates()
    // 定義初始狀態
    .initial(RegStatusEnum.UNCONNECTED)
    // 定義狀態機狀態
    .states(EnumSet.allOf(RegStatusEnum.class));
}複製代碼

咱們須要初始化當前狀態機有哪些狀態事件。其中, source 指定原始狀態,target 指定目標狀態,event 指定觸發事件。

@Override
public void configure(StateMachineTransitionConfigurer<RegStatusEnum, RegEventEnum> transitions)
        throws Exception {
        // 1.鏈接事件
        // 未鏈接 -> 已鏈接
        .withExternal()
            .source(RegStatusEnum.UNCONNECTED)
            .target(RegStatusEnum.CONNECTED)
            .event(RegEventEnum.CONNECT)
        .and() 
        .withExternal()
            .source(RegStatusEnum.CONNECTED)
            .target(RegStatusEnum.CONNECTED)
            .event(RegEventEnum.CONNECT)
        .and()                    

        // 2.註冊事件   
        // 已鏈接 -> 註冊中
        .withExternal()
            .source(RegStatusEnum.CONNECTED)
            .target(RegStatusEnum.REGISTERING)
            .event(RegEventEnum.REGISTER)
        .and()
       .withExternal()
            .source(RegStatusEnum.REGISTERING)
            .target(RegStatusEnum.REGISTERING)
            .event(RegEventEnum.REGISTER)
        .and() 

        // 3.註冊成功事件   
        // 註冊中 -> 已註冊
        .withExternal()
            .source(RegStatusEnum.REGISTERING)
            .target(RegStatusEnum.REGISTERED)
            .event(RegEventEnum.REGISTER_SUCCESS)
        .and()
        .withExternal()
            .source(RegStatusEnum.REGISTERED)
            .target(RegStatusEnum.REGISTERED)
            .event(RegEventEnum.REGISTER_SUCCESS)
        .and()

         // 4.註冊失敗事件   
        // 註冊中 -> 未鏈接
        .withExternal()
            .source(RegStatusEnum.REGISTERING)
            .target(RegStatusEnum.UNCONNECTED)
            .event(RegEventEnum.REGISTER_FAILED)
        .and()

        // 5.註銷事件
        // 已鏈接 -> 未鏈接
        .withExternal()
            .source(RegStatusEnum.CONNECTED)
            .target(RegStatusEnum.UNCONNECTED)
            .event(RegEventEnum.UN_REGISTER)
        .and()
        // 註冊中 -> 未鏈接
        .withExternal()
            .source(RegStatusEnum.REGISTERING)
            .target(RegStatusEnum.UNCONNECTED)
            .event(RegEventEnum.UN_REGISTER)
        .and()
        // 已註冊 -> 未鏈接
        .withExternal()
            .source(RegStatusEnum.REGISTERED)
            .target(RegStatusEnum.UNCONNECTED)
            .event(RegEventEnum.UN_REGISTER)
        ;
}複製代碼

Spring StateMachine 提供了註解配置實現方式,全部 StateMachineListener 接口中定義的事件都能經過註解的方式來進行配置實現。這裏以鏈接事件爲案例,@OnTransition 中 source 指定原始狀態,target 指定目標狀態,當事件觸發時將會被監聽到從而調用 connect() 方法。

@WithStateMachine
public class StateMachineEventConfig {

    @OnTransition(source = "UNCONNECTED", target = "CONNECTED")
    public void connect() {
        System.out.println("///////////////////");
        System.out.println("鏈接事件, 未鏈接 -> 已鏈接");
        System.out.println("///////////////////");
    }

    @OnTransition(source = "CONNECTED", target = "REGISTERING")
    public void register() {
        System.out.println("///////////////////");
        System.out.println("註冊事件, 已鏈接 -> 註冊中");
        System.out.println("///////////////////");
    }

    @OnTransition(source = "REGISTERING", target = "REGISTERED")
    public void registerSuccess() {
        System.out.println("///////////////////");
        System.out.println("註冊成功事件, 註冊中 -> 已註冊");
        System.out.println("///////////////////");
    }

    @OnTransition(source = "REGISTERED", target = "UNCONNECTED")
    public void unRegister() {
        System.out.println("///////////////////");
        System.out.println("註銷事件, 已註冊 -> 未鏈接");
        System.out.println("///////////////////");
    }
}複製代碼

Spring StateMachine 讓狀態機結構更加層次化,咱們來回顧下幾個核心步驟:第一步,定義狀態枚舉。第二步,定義事件枚舉。第三步,定義狀態機配置,設置初始狀態,以及狀態與事件之間的關係。第四步,定義狀態監聽器,當狀態變動時,觸發方法。

(完)

更多精彩文章,盡在「服務端思惟」微信公衆號!

相關文章
相關標籤/搜索