状态机中,每个状态有着相应的行为,随着行为的触发来切换状态。其中一种做法是使用二维数组实现状态机机制,其中横坐标表示行为,纵坐标表示状态,具体的数值则表示当前的状态。

我们以登录场景设计一个状态机。

图片

这时,我们设计一张状态机表。

图片

那么,此时它的二维数组,如下所示。

图片

此外,我们也可以通过状态模式实现一个状态机。状态模式将每一个状态封装成独立的类,具体行为会随着内部状态而改变。状态模式用类表示状态,这样我们就能通过切换类来方便地改变对象的状态,避免了冗长的条件分支语句,让系统具有更好的灵活性和可扩展性。

现在,我们定义一个状态枚举,其中包括未连接、已连接、注册中、已注册 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 依赖。

<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@EnableStateMachinepublic class StateMachineConfig extends EnumStateMachineConfigurerAdapter<RegStatusEnum, RegEventEnum> {}

我们需要初始化状态机的状态。其中,initial(RegStatusEnum.UNCONNECTED) 定义了初始状态是未连接状态。states(EnumSet.allOf(RegStatusEnum.class)) 定义了状态机中存在的所有状态。

@Overridepublic void configure(StateMachineStateConfigurer<RegStatusEnum, RegEventEnum> states) throws Exception {    states.withStates()    // 定义初始状态    .initial(RegStatusEnum.UNCONNECTED)    // 定义状态机状态    .states(EnumSet.allOf(RegStatusEnum.class));}

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

@Overridepublic 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() 方法。

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


更多相关文章

  1. 客户端请求服务器时的状态码讲解
  2. 设计模式之状态模式
  3. 最近状态不咋好 ...
  4. 共享可变状态中出现的问题以及如何避免[每日前端夜话0xDB]
  5. Java线程之线程状态的转换
  6. Spring 中的事件机制,芳芳用过都说好~
  7. 实现一个简单的 JavaScript 状态机[每日前端夜话0xBF]
  8. 从事件监听器OnClick中排除按钮
  9. 在不触发hashchange事件的情况下更改哈希

随机推荐

  1. 深入理解XML中的字符实体与字符数据
  2. 详解SQL对Xml字段的操作示例代码(图)
  3. XML对代码中的空白处理详细介绍
  4. 详解Android实现XML解析技术(图)
  5. XML中处理指令的代码详解
  6. 详细介绍XML Web Service图文代码实例
  7. 详细介绍XML代码编写的编码与验证问题
  8. XSLT语法—在.net中使用XSLT转换xml文档
  9. XML中的DTD文档类型定义详细介绍
  10. XmlSerializer 对象的Xml序列化和反序列