服務(wù)端指南 | 狀態(tài)機設(shè)計

原文地址:服務(wù)端指南 | 狀態(tài)機設(shè)計
博客地址:http://blog.720ui.com/

狀態(tài)機中,每個狀態(tài)有著相應(yīng)的行為,隨著行為的觸發(fā)來切換狀態(tài)。其中一種做法是使用二維數(shù)組實現(xiàn)狀態(tài)機機制疑务,其中橫坐標(biāo)表示行為,縱坐標(biāo)表示狀態(tài)梗醇,具體的數(shù)值則表示當(dāng)前的狀態(tài)知允。

我們以登錄場景設(shè)計一個狀態(tài)機。

這時叙谨,我們設(shè)計一張狀態(tài)機表温鸽。

那么,此時它的二維數(shù)組手负,如下所示涤垫。

此外,我們也可以通過狀態(tài)模式實現(xiàn)一個狀態(tài)機竟终。狀態(tài)模式將每一個狀態(tài)封裝成獨立的類蝠猬,具體行為會隨著內(nèi)部狀態(tài)而改變。狀態(tài)模式用類表示狀態(tài)衡楞,這樣我們就能通過切換類來方便地改變對象的狀態(tài)吱雏,避免了冗長的條件分支語句,讓系統(tǒng)具有更好的靈活性和可擴展性瘾境。

現(xiàn)在歧杏,我們定義一個狀態(tài)枚舉,其中包括未連接迷守、已連接犬绒、注冊中、已注冊 4 種狀態(tài)兑凿。

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;}
}

定義一個環(huán)境類凯力,它是實際上是真正擁有狀態(tài)的對象。

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;
    }
}

狀態(tài)模式用類表示狀態(tài)礼华,這樣我們就能通過切換類來方便地改變對象的狀態(tài)「篮祝現(xiàn)在,我們定義幾個狀態(tài)類圣絮。

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();
    }
}

注意的是祈惶,如果某個行為不會觸發(fā)狀態(tài)的變化,我們可以拋出一個 RuntimeException 異常。此外捧请,調(diào)用時凡涩,通過環(huán)境類控制狀態(tài)的切換,如下所示疹蛉。

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

Spring StateMachine 讓狀態(tài)機結(jié)構(gòu)更加層次化活箕,可以幫助開發(fā)者簡化狀態(tài)機的開發(fā)過程。現(xiàn)在可款,我們來用 Spring StateMachine 進(jìn)行改造育韩。修改 pom 文件,添加 Maven 依賴闺鲸。

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

定義一個狀態(tài)枚舉座慰,其中包括未連接、已連接翠拣、注冊中版仔、已注冊 4 種狀態(tài)。

public enum RegStatusEnum {
    // 未連接
    UNCONNECTED,
    // 已連接
    CONNECTED,
    // 注冊中
    REGISTERING,
    // 已注冊
    REGISTERED;
}

定義一個行為枚舉误墓,其中包括連接蛮粮、注冊、注冊成功谜慌、注冊失敗然想、注銷 5 種行為事件。

public enum RegEventEnum {
    // 連接
    CONNECT,
    // 注冊
    REGISTER,
    // 注冊成功
    REGISTER_SUCCESS,
    // 注冊失敗
    REGISTER_FAILED,
    // 注銷
    UN_REGISTER;
}

接著欣范,我們需要進(jìn)行狀態(tài)機配置变泄,其中 @EnableStateMachine 注解,標(biāo)識啟用 Spring StateMachine 狀態(tài)機功能恼琼。

@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<RegStatusEnum, RegEventEnum> {
}

我們需要初始化狀態(tài)機的狀態(tài)妨蛹。其中,initial(RegStatusEnum.UNCONNECTED) 定義了初始狀態(tài)是未連接狀態(tài)晴竞。states(EnumSet.allOf(RegStatusEnum.class)) 定義了狀態(tài)機中存在的所有狀態(tài)蛙卤。

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

我們需要初始化當(dāng)前狀態(tài)機有哪些狀態(tài)事件。其中噩死, source 指定原始狀態(tài)颤难,target 指定目標(biāo)狀態(tài),event 指定觸發(fā)事件已维。

@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 提供了注解配置實現(xiàn)方式既琴,所有 StateMachineListener 接口中定義的事件都能通過注解的方式來進(jìn)行配置實現(xiàn)。這里以連接事件為案例,@OnTransition 中 source 指定原始狀態(tài)松靡,target 指定目標(biāo)狀態(tài),當(dāng)事件觸發(fā)時將會被監(jiān)聽到從而調(diào)用 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 讓狀態(tài)機結(jié)構(gòu)更加層次化,我們來回顧下幾個核心步驟:第一步,定義狀態(tài)枚舉浦旱。第二步例隆,定義事件枚舉皿曲。第三步,定義狀態(tài)機配置劫樟,設(shè)置初始狀態(tài)易阳,以及狀態(tài)與事件之間的關(guān)系拒课。第四步,定義狀態(tài)監(jiān)聽器扎酷,當(dāng)狀態(tài)變更時,觸發(fā)方法遏匆。

(完)

更多精彩文章法挨,盡在「服務(wù)端思維」微信公眾號!


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末幅聘,一起剝皮案震驚了整個濱河市凡纳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帝蒿,老刑警劉巖荐糜,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異葛超,居然都是意外死亡暴氏,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門绣张,熙熙樓的掌柜王于貴愁眉苦臉地迎上來答渔,“玉大人,你說我怎么就攤上這事侥涵≌铀海” “怎么了宋雏?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長务豺。 經(jīng)常有香客問我磨总,道長,這世上最難降的妖魔是什么笼沥? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任蚪燕,我火速辦了婚禮,結(jié)果婚禮上敬拓,老公的妹妹穿的比我還像新娘。我一直安慰自己裙戏,他們只是感情好乘凸,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著累榜,像睡著了一般营勤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上壹罚,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天葛作,我揣著相機與錄音,去河邊找鬼猖凛。 笑死赂蠢,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的辨泳。 我是一名探鬼主播虱岂,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼菠红!你這毒婦竟也來了第岖?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤试溯,失蹤者是張志新(化名)和其女友劉穎蔑滓,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體遇绞,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡键袱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了摹闽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片杠纵。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖钩骇,靈堂內(nèi)的尸體忽然破棺而出比藻,到底是詐尸還是另有隱情铝量,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布银亲,位于F島的核電站慢叨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏务蝠。R本人自食惡果不足惜拍谐,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望馏段。 院中可真熱鬧轩拨,春花似錦、人聲如沸院喜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽喷舀。三九已至砍濒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間硫麻,已是汗流浹背爸邢。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拿愧,地道東北人杠河。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像浇辜,于是被迫代替她去往敵國和親感猛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理奢赂,服務(wù)發(fā)現(xiàn)陪白,斷路器,智...
    卡卡羅2017閱讀 134,656評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,810評論 6 342
  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 10,969評論 6 13