资讯专栏INFORMATION COLUMN

服务端指南 | 状态机设计

AWang / 1700人阅读

摘要:我们以登录场景设计一个状态机。第三步,定义状态机配置,设置初始状态,以及状态与事件之间的关系。

原文地址:梁桂钊的博客

博客地址: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(StateMachineStateConfigurer states) throws Exception {
    states.withStates()
    // 定义初始状态
    .initial(RegStatusEnum.UNCONNECTED)
    // 定义状态机状态
    .states(EnumSet.allOf(RegStatusEnum.class));
}

我们需要初始化当前状态机有哪些状态事件。其中, source 指定原始状态,target 指定目标状态,event 指定触发事件。

@Override
public void configure(StateMachineTransitionConfigurer 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 让状态机结构更加层次化,我们来回顾下几个核心步骤:第一步,定义状态枚举。第二步,定义事件枚举。第三步,定义状态机配置,设置初始状态,以及状态与事件之间的关系。第四步,定义状态监听器,当状态变更时,触发方法。

(完)

更多精彩文章,尽在「服务端思维」微信公众号!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68216.html

相关文章

  • 2019 - Web开发技术指南和趋势

    摘要:以下内容来自我特别喜欢的一个频道这是一个年你成为前端,后端或全栈开发者的进阶指南你不需要学习所有的技术成为一个开发者这个指南只是通过简单分类列出了技术选项我将从我的经验和参考中给出建议首选我们会介绍通用的知识最后介绍年的的一些趋势基础前端开 以下内容来自我特别喜欢的一个Youtube频道: Traversy Media 这是一个2019年你成为前端,后端或全栈开发者的进阶指南: 你...

    sourcenode 评论0 收藏0
  • 架构

    摘要:分布式架构实践负载均衡在网站创立初期,我们一般都使用单台机器对台提供集中式服务,但是随着业务量越来越大,无论是性能上还是稳定性上都有了更大的挑战。就鹿晗宣布恋情导致微博宕机事件浅谈大型网站高可用性架构中午吃饭刷着刷着微博发现微博突然挂了。 分布式架构实践——负载均衡 在网站创立初期,我们一般都使用单台机器对台提供集中式服务,但是随着业务量越来越大,无论是性能上还是稳定性上都有了更大的挑...

    Pink 评论0 收藏0
  • Java入坑指南

    摘要:入坑指南是滴,下面是一个最低的入坑还应该有种设计模式应该掌握的。堆栈以帧为单位保存线程的状态,对堆栈的操作为压栈和出栈执行字节码以后,将会产生程序计数器和栈,程序计数器存放将要执行下一条指令的偏移量。 Java入坑指南是滴,下面是一个最低的入坑 还应该有23种设计模式应该掌握的。╮(╯▽╰)╭注意,第一个j是大写。 Java的特点跨平台,风格接近C++最重要的api文档 https:/...

    Rindia 评论0 收藏0
  • 好书阅读与推荐(续四)

    摘要:后端好书阅读与推荐系列文章后端好书阅读与推荐后端好书阅读与推荐续后端好书阅读与推荐续二后端好书阅读与推荐续三后端好书阅读与推荐续四这里依然记录一下每本书的亮点与自己读书心得和体会,分享并求拍砖。 后端好书阅读与推荐系列文章:后端好书阅读与推荐后端好书阅读与推荐(续)后端好书阅读与推荐(续二)后端好书阅读与推荐(续三)后端好书阅读与推荐(续四) 这里依然记录一下每本书的亮点与自己读书心得...

    phodal 评论0 收藏0
  • spring statemachine的企业可用级开发指南7-伪持久化和中间段状态

    摘要:在实际的企业开发中,不可能所有情况都是从头到尾的按状态流程来,会有很多意外,比如历史数据,故障重启后的遗留流程,所以这种可以任意调节状态的才是我们需要的状态机。 1、伪持久化和中间段的状态机我们设想一个业务场景,就比如订单吧,我们一般的设计都会把订单状态存到订单表里面,其他的业务信息也都有表保存,而状态机的主要作用其实是规范整个订单业务流程的状态和事件,所以状态机要不要保存真的不重要,...

    shiyang6017 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<