状态模式 & Spring Statemachine

状态模式

状态模式(State Pattern)是一种行为设计模式,它允许一个对象在其内部状态改变时改变它的行为。通过将状态封装成独立的类,并将行为委托给代表当前状态的对象,从而实现状态与行为的分离。

关键组件:

  1. 上下文(Context)
    • 维护一个指向当前状态的引用,用来表示当前状态
    • 提供修改状态的方法,允许状态对象自行切换到其他状态。
    • 可向外界提供一些接口,用于触发状态对应的行为。
  2. 抽象状态类(State)
    • 定义一个接口以封装与环境类的一个特定状态相关的行为。
    • 封装一些方法处理上下文的请求,这些请求根据对象的状态有不同的实现
  3. 具体状态类(Concrete State)
    • 每个类对应于环境类的一个具体状态,并实现该状态下应有的行为。
    • 可以在需要时改变Context 的状态,由于Context 实例通常作为参数传递给具体状态对象,所以具体状态对象可以根据环境的情况决定转换到其他状态。

状态模式的工作流程

  1. 请求处理
    1. Context 接收到请求后,将请求下发给当前状态的对象来处理
    2. Context 本身不决定如何响应各种请求,所有的决策权都在Concrete State
  2. 状态转换
    1. 处理请求的过程中,状态可以判断是否需要转到另一个状态
    2. 状态转换通常是通过调用Context 提供的方法来实现,这些方法会改变上下文持有的State 对象
  3. 行为变更
    1. 一旦状态发生变化,随后的请求就会根据新的状态来处理,从而改变对象的行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// State interface
interface State {
void handleRequest();
}

// Concrete states
class ConcreteStateA implements State {
public void handleQequest() {
System.out.println("State A handling request.");
}
}

class ConcreteStateB implements State {
public void handleRequest() {
System.out.println("State B handling request.");
}
}

// Context class
class Context {
private State currentState;

public Context(State state) {
this.currentState = state;
}

public void setState(State state) {
this.currentState = state;
}

public void request() {
currentState.handleRequest();
}
}

// Usage
public class Main {
public static void main(String[] args) {
Context context = new Context(new ConcreteStateA());
context.request(); // Output: State A handling request.
context.setState(new ConcreteStateB());
context.request(); // Output: State B handling handling request.
}
}

Spring StateMachine

Spring StateMachine 是一个基于 Spring Framework 的库,专门用于管理状态机,并在 Spring 应用中实现状态模式。

主要特性

Spring StateMachine 提供以下主要特性:

  • 状态管理:支持状态的定义、状态转换以及状态间的事件触发。
  • 事件处理:基于事件驱动,可以响应并处理来自应用程序的各种事件。
  • 状态机监听:允许开发者添加监听器来监控状态机的状态变化或事件触发。
  • 持久化:支持状态机的持久化,使得状态机即使在应用重启后也能恢复到之前的状态。
  • 易于集成:可以轻松地与 Spring 应用集成,利用 Spring 的依赖注入等特性。

使用场景

Spring StateMachine 非常适合处理复杂的业务流程和状态管理问题,例如:

  • 订单处理流程(如订单创建、支付、发货、完成)
  • 工作流管理
  • 游戏逻辑的状态控制
  • 设备的状态监控系统

基本概念

在使用 Spring StateMachine 时,主要涉及以下几个概念:

  1. 状态(State)

    • 系统的某个具体状态,如订单的“已支付”、“未支付”状态。
  2. 事件(Event)

    • 触发状态转换的行为,如“支付成功”事件可能会将订单状态从“未支付”改变为“已支付”。
  3. 转换(Transition)

    • 状态之间的迁移规则,定义了在什么事件下,从哪个状态转移到哪个状态。
  4. 动作(Action)

    • 在状态转换发生时执行的业务逻辑,比如在订单支付成功时发送一封确认邮件。
    • 进入动作:进入状态时的执行
    • 退出动作:退出状态时的执行
    • 转换动作:因事件而发生状态转换的执行
  5. 守卫(Guard)

  • 评估为真才能进行状态换船的条件,如果不满足条件则不进行转换

Spring StateMachine 为管理复杂的状态逻辑提供了一个结构化和强大的解决方案,它使得状态转换逻辑更加清晰,并且易于维护。通过集成到 Spring 框架中,它也可以利用 Spring 提供的其他功能,如依赖注入和事务管理,从而使开发工作更加便捷。

实践

订单状态

订单状态

1
2
3
4
5
6
7
8
9
10
11
12
public enum States {
UNPAID, // 待支付
WAITING_FOR_RECEIVE, // 待收货
DONE, // 已完成
CANCELED // 已取消
}

public enum Events {
PAY, // 支付
RECEIVE, // 收货
CANCEL // 取消
}

配置状态机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Configuration
@EnableStateMachine
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> {

@Override
public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception {
states
.withStates()
.initial(States.UNPAID)
.state(States.WAITING_FOR_RECEIVE)
.end(States.DONE)
.end(States.CANCELED);
}

@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
transitions
.withExternal()
.source(States.UNPAID).target(States.WAITING_FOR_RECEIVE).event(Events.PAY)
.and()
.withExternal()
.source(States.WAITING_FOR_RECEIVE).target(States.DONE).event(Events.RECEIVE)
.and()
.withExternal()
.source(States.UNPAID).target(States.CANCELED).event(Events.CANCEL)
.and()
.withExternal()
.source(States.WAITING_FOR_RECEIVE).target(States.CANCELED).event(Events.CANCEL);
}

@Bean
public StateMachineListener<States, Events> listener() {
return new StateMachineListenerAdapter<States, Events>() {
@Override
public void stateChanged(State<States, Events> from, State<States, Events> to) {
System.out.println("State changed from " + (from == null ? "none" : from.getId()) + " to " + to.getId());
}
};
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootApplication
public class StateMachineTest {

public static void main(String[] args) {
SpringApplication app = new SpringApplication(StateMachineTest.class);
ApplicationContext context = app.run(args);
StateMachine<States, Events> stateMachine = context.getBean(StateMachine.class);
stateMachine.start();
stateMachine.sendEvent(Events.PAY);
stateMachine.sendEvent(Events.RECEIVE);
}
}

参考