摘要:我們以登錄場景設計一個狀態機。第三步,定義狀態機配置,設置初始狀態,以及狀態與事件之間的關系。
原文地址:梁桂釗的博客博客地址:http://blog.720ui.com/
狀態機中,每個狀態有著相應的行為,隨著行為的觸發來切換狀態。其中一種做法是使用二維數組實現狀態機機制,其中橫坐標表示行為,縱坐標表示狀態,具體的數值則表示當前的狀態。
我們以登錄場景設計一個狀態機。
這時,我們設計一張狀態機表。
那么,此時它的二維數組,如下所示。
此外,我們也可以通過狀態模式實現一個狀態機。狀態模式將每一個狀態封裝成獨立的類,具體行為會隨著內部狀態而改變。狀態模式用類表示狀態,這樣我們就能通過切換類來方便地改變對象的狀態,避免了冗長的條件分支語句,讓系統具有更好的靈活性和可擴展性。
現在,我們定義一個狀態枚舉,其中包括未連接、已連接、注冊中、已注冊 4 種狀態。
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;} }
定義一個環境類,它是實際上是真正擁有狀態的對象。
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 異常。此外,調用時,通過環境類控制狀態的切換,如下所示。
public class Client { public static void main(String[] args) { Context context = new Context(); context.connect(); context.register(); } }
Spring StateMachine 讓狀態機結構更加層次化,可以幫助開發者簡化狀態機的開發過程。現在,我們來用 Spring StateMachine 進行改造。修改 pom 文件,添加 Maven 依賴。
org.springframework.statemachine spring-statemachine-core 1.2.0.RELEASE
定義一個狀態枚舉,其中包括未連接、已連接、注冊中、已注冊 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{ }
我們需要初始化狀態機的狀態。其中,initial(RegStatusEnum.UNCONNECTED) 定義了初始狀態是未連接狀態。states(EnumSet.allOf(RegStatusEnum.class)) 定義了狀態機中存在的所有狀態。
@Override public void configure(StateMachineStateConfigurerstates) throws Exception { states.withStates() // 定義初始狀態 .initial(RegStatusEnum.UNCONNECTED) // 定義狀態機狀態 .states(EnumSet.allOf(RegStatusEnum.class)); }
我們需要初始化當前狀態機有哪些狀態事件。其中, source 指定原始狀態,target 指定目標狀態,event 指定觸發事件。
@Override public void configure(StateMachineTransitionConfigurertransitions) 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("http:///////////////////"); System.out.println("連接事件, 未連接 -> 已連接"); System.out.println("http:///////////////////"); } @OnTransition(source = "CONNECTED", target = "REGISTERING") public void register() { System.out.println("http:///////////////////"); System.out.println("注冊事件, 已連接 -> 注冊中"); System.out.println("http:///////////////////"); } @OnTransition(source = "REGISTERING", target = "REGISTERED") public void registerSuccess() { System.out.println("http:///////////////////"); System.out.println("注冊成功事件, 注冊中 -> 已注冊"); System.out.println("http:///////////////////"); } @OnTransition(source = "REGISTERED", target = "UNCONNECTED") public void unRegister() { System.out.println("http:///////////////////"); System.out.println("注銷事件, 已注冊 -> 未連接"); System.out.println("http:///////////////////"); } }
Spring StateMachine 讓狀態機結構更加層次化,我們來回顧下幾個核心步驟:第一步,定義狀態枚舉。第二步,定義事件枚舉。第三步,定義狀態機配置,設置初始狀態,以及狀態與事件之間的關系。第四步,定義狀態監聽器,當狀態變更時,觸發方法。
(完)
更多精彩文章,盡在「服務端思維」微信公眾號!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68216.html
摘要:以下內容來自我特別喜歡的一個頻道這是一個年你成為前端,后端或全棧開發者的進階指南你不需要學習所有的技術成為一個開發者這個指南只是通過簡單分類列出了技術選項我將從我的經驗和參考中給出建議首選我們會介紹通用的知識最后介紹年的的一些趨勢基礎前端開 以下內容來自我特別喜歡的一個Youtube頻道: Traversy Media 這是一個2019年你成為前端,后端或全棧開發者的進階指南: 你...
摘要:后端好書閱讀與推薦系列文章后端好書閱讀與推薦后端好書閱讀與推薦續后端好書閱讀與推薦續二后端好書閱讀與推薦續三后端好書閱讀與推薦續四這里依然記錄一下每本書的亮點與自己讀書心得和體會,分享并求拍磚。 后端好書閱讀與推薦系列文章:后端好書閱讀與推薦后端好書閱讀與推薦(續)后端好書閱讀與推薦(續二)后端好書閱讀與推薦(續三)后端好書閱讀與推薦(續四) 這里依然記錄一下每本書的亮點與自己讀書心得...
摘要:在實際的企業開發中,不可能所有情況都是從頭到尾的按狀態流程來,會有很多意外,比如歷史數據,故障重啟后的遺留流程,所以這種可以任意調節狀態的才是我們需要的狀態機。 1、偽持久化和中間段的狀態機我們設想一個業務場景,就比如訂單吧,我們一般的設計都會把訂單狀態存到訂單表里面,其他的業務信息也都有表保存,而狀態機的主要作用其實是規范整個訂單業務流程的狀態和事件,所以狀態機要不要保存真的不重要,...
閱讀 2737·2021-10-09 09:44
閱讀 3550·2019-08-30 15:54
閱讀 2160·2019-08-30 14:16
閱讀 2790·2019-08-30 13:09
閱讀 825·2019-08-30 13:08
閱讀 1280·2019-08-29 16:29
閱讀 1662·2019-08-26 13:57
閱讀 1925·2019-08-26 13:53