文章目录
一、基础概念
1.1 状态模式
状态模式(State Pattern)是一种行为型设计模式,它允许对象在内部状态发生变化时改变其行为。在状态模式中,一个对象的行为取决于其当前状态,而且可以随时改变这个状态。状态模式将对象的状态封装在不同的状态类中,从而使代码更加清晰和易于维护。当一个对象的状态改变时,状态模式会自动更新该对象的行为,而不需要在代码中手动进行判断和处理。
1.2 状态机图
使用UML状态机图能够简洁直观地描述出行为状态的转移,它包含七种元素:
- 起始状态:用实心圆表示
- 终止状态:内部实心的同心圆表示
- 状态:用椭圆或圆角矩形表示,内部要填写状态名
- 状态转移:用连接状态的箭头表示,指从箭头起点状态转移到箭头终点状态的状态转移
- 事件:用箭头上的文字描述表示,指引起状态转移的原因,对应一种特定的状态转移规则
- 条件:用事件名之后括号括起来的文字表示,指状态转换的条件(非必须)
- 动作:转换激活时的操作(非必须)
状态机图的示例如下:(配图来源:设计模式:一目了然的状态机图)
在当前状态的基础上,当发生事件时,会判断是否满足状态转移的条件,满足时就会转移到对应的状态,完成状态转移后可以执行预设的动作。
1.3 状态机(模型/引擎)
状态机,也就是 State Machine,不是指一台实际机器,而是指一个描述状态转移的数学模型。状态机是状态模式的一种应用,相当于上下文角色的一个升级版。在工作流或游戏等各种系统中有大量使用,如各种工作流引擎,它几乎是状态机的子集和实现,封装状态的变化规则。状态机帮助开发者简化状态控制的开发过程,让状态机结构更加层次化。
开源的状态机框架非常多,如Spring就提供了状态机组件,名称就是状态机(State Machine)。很多状态机框架虽然功能齐全,但是过于复杂,性能也较差。从功能足够用、使用起来足够简单、性能足够好的角度出发,推荐使用Cola Statemachine。本文以该Cola Statemachine为例介绍状态机引擎的使用。
二、状态机(Cola Statemachine)的使用
2.1 相关的核心概念
- State:状态
- Transition:流转
- External Transition:外部流转,指两个不同状态之间的流转
- Internal Transition:内部流转,同一个状态之间的流转
- Event:事件,指引起状态流传的事件
- Condition:条件,表示是否允许到达某个状态
- Action:动作,指到达某个状态后触发的动作
- StateMachine:状态机
2.2 状态机的的使用流程
- 注册状态机并定定义状态流转
- 获取状态机并使用
2.2.1 注册状态机并定义状态流转
需要注意的是,API的调用顺序只能遵循如下的顺序,包含三种transition方式。
由于该状态机的实现是无状态的,所以可以使用同一个状态机实例来定义多个状态流转。
StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
// 当发生Events.EVENT1事件,且满足checkCondition()条件时,状态States.STATE1将流转为状态States.STATE2,并触发doAction()动作
builder.externalTransition()
.from(States.STATE1)
.to(States.STATE2)
.on(Events.EVENT1)
.when(condition)
.perform(action);
// 相当于多种from状态到同个终止状态的批量配置
// 状态流转的定义,可以同时定义多种起始状态在同一事件发生时转移到相同的状态,并执行相同的动作
// 即,对于某事件而言,起始状态可以多个,终止状态和行为对应一个
builder.externalTransitions()
.from(States.STATE1, States.STATE2)
.to(States.STATE3)
.on(Events.EVENT1)
.when(condition)
.perform(action);
// 当发生Events.EVENT1事件,且满足checkCondition()条件时,状态States.STATE2将流转为状态States.STATE2,并触发doAction()动作
builder.internalTransition()
.within(States.STATE2)
.on(Events.INTERNAL_EVENT)
.when(condition)
.perform(action);
// 使用一个String类型的machineId来构建状态机实例
builder.build(machineId);
// 之后再使用machineId来获取指定的状态机实例
StateMachine<States, Events, Context> stateMachine = StateMachineFactory.get(machineId);
stateMachine.showStateMachine();
2.2.2 获取状态机并使用
// 1.获取状态机
StateMachine<States, Events, Context> stateMachine = getStateMachine();
// 2.组装上下文 messageContext
Context context = buildContext();
// 3.使用状态机调用fireEvent来触发事件:源状态,事件,上下文(供checkCondition()和doAction()使用)
stateMachine.fireEvent(States.STATE1, Events.EVENT1, context);
// 补充:可以打印状态机中定义的状态转移信息
String plantUML = orderOperaMachine.generatePlantUML();
System.out.println(plantUML);
三、补充的详细说明
3.1 when(Condition<C> condition)
when(condition)
会调用condition对象的boolean isSatisfied(C context)
方法,根据上下文判断是否满足条件,满足条件时才会执行状态流转。
3.2 perform(Action<S, E, C> action)
perform(action)
会调用action对象的public void execute(S from, S to, E event, C context)
方法,在满足条件并执行了状态流转后,去执行相应的动作。
四、状态机的使用小结
我们先定义好状态转移规则和相应的触发动作,之后只需要调用状态机的方法来完成状态更新。状态机会根据源状态、事件来使用相应的状态转移规则,根据上下文中的信息判断是否满足转移的条件,满足时就会转移到目标状态并触发预设的动作。
参考博客推荐
设计模式:一目了然的状态机图
玩转 Spring 状态机 | 京东云技术团队
实现一个状态机引擎,教你看清DSL的本质
COLA中的cola-statemachine状态机理解与使用例