状态机的介绍和使用
状态机介绍
我们在开发过程中,发现一些场景优化可以很明显的使用状态机模式对模型进行状态的转移, 状态模式也是我们在开发的过程中常用的模式, 毕竟写起来很简单 ,有用一个枚举就可以表达. 该文章给大家介绍下状态机相关的知识点
状态模式
状态模式,又称状态对象模式(Pattern of Objects for States),状态模式是对象的行为模式。状态模式允许一个对象在其内部状态改变的时候改变其行为。这个对象看上去就像是改变了它的类一样。
状态模式所涉及到的角色有:
- 环境(Context)角色,也成上下文:定义客户端所感兴趣的接口,并且保留一个具体状态类的实例。这个具体状态类的实例给出此环境对象的现有状态。
- 抽象状态(State)角色:定义一个接口,用以封装环境(Context)对象的一个特定的状态所对应的
- 具体状态(ConcreteState)角色:每一个具体状态类都实现了环境(Context)的一个状态所对应的行为。
状态模式demo
-
context
public class Context { private State state; public void doRequest(String cmd) { System.out.println(state.invoke(cmd)); } public void setState(State state) { this.state = state; } }
-
state
public interface State { String invoke(String name); } // 子类 public class QueryCmd implements State{ @Override public String invoke(String name) { return "this is a queryCMD:" + name ; } } public class UpdateCmd implements State{ @Override public String invoke(String name) { return "this is a updateCMD:" + name ; } }
-
客户端
public static void main(String[] args) { State state = new QueryCmd(); Context context = new Context(); context.setState(state); context.doRequest("查询"); }
环境类Context的行为request()是委派给某一个具体状态类的。通过使用多态性原则,可以动态改变环境类Context的属性State的内容,使其从指向一个具体状态类变换到指向另一个具体状态类,从而使环境类的行为dorequest()由不同的具体状态类来执行。
状态机
状态机是状态模式的一种运用,是一组状态的集合,是协调相关信号动作,完成特定操作的控制中心。状态机可归纳为4个要素,即当前状态,条件,动作,下个状态。这样的归纳主要出于对状态机的内在因果关系的考虑,当前状态和条件是因,动作和下个状态是果。对于复杂些的逻辑,用状态机会有助于代码比较清晰,容易维护和调试
复杂度对比
在github上TOP2 的状态机是 spring 状态机 和 squirrel ,这2个开源框架功能都十分强大, 但是结合自身业务要求 ,其实用不上他们强劲的功能,反而在一些简单场景下,使用的上手难度反而增高,于是把目光投向了COLA state Machine,,在作者博客中有这种说法
在合适的地方用合适的解决方案,不能一招鲜吃遍天。就像最臭名昭著的DSL——流程引擎,就属于那种严重的被滥用和过渡设计的典型,是把简单的问题复杂化的典型。
最好不要无端增加复杂性。然而,想做简单也不是一件容易的事,特别是在大公司,我们不仅要写代码,还要能沉淀“NB的技术”,最好是那种可以把老板说的一愣一愣的技术
我所需要的状态机,也仅仅需要将状态转移并对数据进行持久化,使用复杂度当然是越简单越好了
性能优化对比
COLA状态机 还有一个概念,全程操作完全可以是无状态的, 市面上的开源状态机引擎,不难发现,它们之所以有状态,主要是在状态机里面维护了两个状态:初始状态(initial state)和当前状态(current state),如果我们能把这两个实例变量去掉的话,就可以实现无状态,从而实现一个状态机只需要有一个instance就够了。当然这样就没办法获取到状态机instance的current state,不过在我使用的过程中也不需要知道,这样一来,一个单例就可以维护整个状态的流程,性能大大提高
COLA状态机名词概念
- State:状态
- Event:事件,状态由事件触发,引起变化
- Transition:流转,表示从一个状态到另一个状态
- External Transition:外部流转,两个不同状态之间的流转
- Internal Transition:内部流转,同一个状态之间的流转
- Condition:条件,表示是否允许到达某个状态
- Action:动作,到达某个状态之后,可以做什么
- StateMachine:状态机
流程解析
COLA实现的状态机十分之简单,作者说是利用空余时间写的, 整体项目文件数量不超过30个,大部分都是interface,结构清晰易懂
- 结构图示
COLA 状态机demo
下面写了个demo展示.具体内容参考的是test源码中的内容
State枚举
package com.example.demo.statemachine;
import lombok.Getter;
public enum TaskStatusEnum {
/**
* 任务入库
*/
INIT(1),
/**
* 任务完成-发送积分
*/
FINISHED(2),
/**
* 任务取消
*/
CANCEL(3),
/**
* 任务完成-即将发送积分
*/
WILL_SEND(4),
;
@Getter
private Integer type;
TaskStatusEnum(Integer type) {
this.type = type;
}
}
Event枚举
public enum TaskTypeEnums {
/**
*
*/
VIDEO(4, "视频"),
NOVEL(5, "小说"),
;
@Getter
private Integer code;
@Getter
private String name;
TaskTypeEnums(int i, String name) {
this.code = i;
this.name = name;
}
public static TaskTypeEnums findByType(Integer taskType) {
for (TaskTypeEnums value : values()) {
if (value.code.equals(taskType)) {
return value;
}
}
return null;
}
}
context领域模型
public interface Context {
String getContent();
String getId();
}
// 实现类
public class Init2WillSendContext implements Context {
@Override
public String getContent() {
return "123";
}
@Override
public String getId(){
return "Task";
}
}
客户端
public class StateMachineHepler {
public static void main(String[] args) {
new StateMachineHepler().doTest();
}
public void doTest() {
StateMachineBuilder<TaskStatusEnum, TaskTypeEnums, Init2WillSendContext> builder
= StateMachineBuilderFactory.create();
builder.externalTransition()
.from(TaskStatusEnum.INIT)
.to(TaskStatusEnum.WILL_SEND)
.on(TaskTypeEnums.NOVEL)
.when(checkCondition())
.perform(doAction());
builder.build("task");
StateMachineFactory.get("task")
.fireEvent(TaskStatusEnum.INIT, TaskTypeEnums.NOVEL, new Init2WillSendContext());
}
public boolean check() {
return true;
}
private Condition<Init2WillSendContext> checkCondition() {
return (ctx) -> ctx.getId().equals("Task");
}
private Action<TaskStatusEnum, TaskTypeEnums, Init2WillSendContext> doAction() {
return (from, to, event, ctx) -> {
System.out.println(ctx.getContent() + " is operating " + ctx.getId() + " from:" + from + " to:" + to + " on:" + event);
};
}
}
以上代码执行后能清晰看到状态从INIT 状态转移到了WILL_SEND 状态,其中判断条件是啥.实用的场景
代码解析
首先是com.alibaba.cola.statemachine.builder.StateMachineBuilderImpl
// 一共是提供2个功能。
// 1. 选择用哪个Transaction去初始化
// 2. 将初始化的值state Machine 注册到factory中,后面可以直接通过id静态获取对应注册的状态机
public class StateMachineBuilderImpl<S, E, C> implements StateMachineBuilder<S, E, C> {
/**
* StateMap is the same with stateMachine, as the core of state machine is holding reference to states.
*/
private final Map<S, State< S, E, C>> stateMap = new ConcurrentHashMap<>();
private final StateMachineImpl<S, E, C> stateMachine = new StateMachineImpl<>(stateMap);
/**
外部扩展的状态流转
*/
@Override
public ExternalTransitionBuilder<S, E, C> externalTransition() {
return new TransitionBuilderImpl<>(stateMap, TransitionType.EXTERNAL);
}
/**
可满足多个状态时都去执行
*/
@Override
public ExternalTransitionsBuilder<S, E, C> externalTransitions() {
return new TransitionsBuilderImpl<>(stateMap, TransitionType.EXTERNAL);
}
/**
内部扩展的状态流转
*/
@Override
public InternalTransitionBuilder<S, E, C> internalTransition() {
return new TransitionBuilderImpl<>(stateMap, TransitionType.INTERNAL);
}
/**
*加到状态机factory中
*/
@Override
public StateMachine<S, E, C> build(String machineId) {
stateMachine.setMachineId(machineId);
stateMachine.setReady(true);
StateMachineFactory.register(stateMachine);
return stateMachine;
}
}
构建出状态流转实体
// 状态流转的构建类
class TransitionBuilderImpl<S,E,C> implements ExternalTransitionBuilder<S,E,C>, InternalTransitionBuilder<S,E,C>, From<S,E,C>, On<S,E,C>, To<S,E,C> {
// 将所有存在的上层传入的State 转成代码中适用的State类
final Map<S, State<S, E, C>> stateMap;
// 当前的state类
private State<S, E, C> source;
// 目前要转向的State 类
protected State<S, E, C> target;
// 状态流转类
private Transition<S, E, C> transition;
// 流程 类型 是 内部流转还是外部流程
final TransitionType transitionType;
// 构造函数, 传入的空map 和当前的流程类型
public TransitionBuilderImpl(Map<S, State<S, E, C>> stateMap, TransitionType transitionType) {
this.stateMap = stateMap;
this.transitionType = transitionType;
}
// from方法
@Override
public From<S, E, C> from(S stateId) {
// 从map里面获取, 看哈 这个state有没有被初始化过,没有的话就初始化, 并在这个map里面缓存上;
source = StateHelper.getState(stateMap, stateId);
return this;
}
// to 方法
@Override
public To<S, E, C> to(S stateId) {
target = StateHelper.getState(stateMap, stateId);
return this;
}
// within 方法 就是同一个状态下的流转
@Override
public To<S, E, C> within(S stateId) {
source = target = StateHelper.getState(stateMap, stateId);
return this;
}
@Override
public When<S, E, C> when(Condition<C> condition) {
transition.setCondition(condition);
return this;
}
/**条件判断
*/
@Override
public On<S, E, C> on(E event) {
transition = source.addTransition(event, target, transitionType);
return this;
}
/** 执行对应方法
*/
@Override
public void perform(Action<S, E, C> action) {
transition.setAction(action);
}
}
后续,开始补充from
,to
,on
,perform
等方法,最后调用build
方法放入本地缓存,然后使用的时候去调用StateMachineFactory#get.fireEvent
public class StateMachineImpl<S, E, C> implements StateMachine<S, E, C> {
// 状态机id
private String machineId;
// 状态机当前已经初始化的statemap
private final Map<S, State<S, E, C>> stateMap;
// 判断当前状态机是不是有build 过
private boolean ready;
// 在build的时候初始化,并且传入已经设置好的几个状态map
public StateMachineImpl(Map<S, State<S, E, C>> stateMap) {
this.stateMap = stateMap;
}
// 执行event
@Override
public S fireEvent(S sourceStateId, E event, C ctx) {
//。首选判断是不是已经有过build
isReady();
//。去获取对应的eventid 和状态流转
Transition<S, E, C> transition = routeTransition(sourceStateId, event, ctx);
if (transition == null) {
Debugger.debug("There is no Transition for " + event);
return sourceStateId;
}
// 返回执行之后的状态
return transition.transit(ctx).getId();
}
private Transition<S, E, C> routeTransition(S sourceStateId, E event, C ctx) {
State sourceState = getState(sourceStateId);
List<Transition<S, E, C>> transitions = sourceState.getEventTransitions(event);
if (transitions == null || transitions.size() == 0) {
return null;
}
Transition<S, E, C> transit = null;
for (Transition<S, E, C> transition : transitions) {
if (transition.getCondition() == null) {
transit = transition;
} else if (transition.getCondition().isSatisfied(ctx)) {
transit = transition;
break;
}
}
return transit;
}
private State getState(S currentStateId) {
State state = StateHelper.getState(stateMap, currentStateId);
if (state == null) {
showStateMachine();
throw new StateMachineException(currentStateId + " is not found, please check state machine");
}
return state;
}
private void isReady() {
if (!ready) {
throw new StateMachineException("State machine is not built yet, can not work");
}
}
@Override
public String accept(Visitor visitor) {
StringBuilder sb = new StringBuilder();
sb.append(visitor.visitOnEntry(this));
for (State state : stateMap.values()) {
sb.append(state.accept(visitor));
}
sb.append(visitor.visitOnExit(this));
return sb.toString();
}
@Override
public void showStateMachine() {
SysOutVisitor sysOutVisitor = new SysOutVisitor();
accept(sysOutVisitor);
}
@Override
public String generatePlantUML() {
PlantUMLVisitor plantUMLVisitor = new PlantUMLVisitor();
return accept(plantUMLVisitor);
}
@Override
public String getMachineId() {
return machineId;
}
public void setMachineId(String machineId) {
this.machineId = machineId;
}
public void setReady(boolean ready) {
this.ready = ready;
}
}
注意一点的是在4.0.0
中有个坑
com.alibaba.cola.statemachine.impl.StateImpl
public class StateImpl<S,E,C> implements State<S,E,C> {
protected final S stateId;
// 这里是使用的map ,key对应event,value 对应transition ,则如果from的state是相同的,在addTransition去put的时候新的会覆盖老的transition
private HashMap<E, Transition<S, E,C>> transitions = new HashMap<>();
StateImpl(S stateId){
this.stateId = stateId;
}
@Override
public Transition<S, E, C> addTransition(E event, State<S,E,C> target, TransitionType transitionType) {
Transition<S, E, C> newTransition = new TransitionImpl<>();
newTransition.setSource(this);
newTransition.setTarget(target);
newTransition.setEvent(event);
newTransition.setType(transitionType);
Debugger.debug("Begin to add new transition: "+ newTransition);
verify(event, newTransition);
transitions.put(event, newTransition);
return newTransition;
}
...
}
而在4.1.0 没有使用这种写法,而是使用的list避免了覆盖问题
这样整个流程就执行完了,整体结构比较小巧,也为我们在改编重构上提供了便利, 如结合srping容器,如添加注解形式的state Machine等.
参考文章
https://blog.csdn.net/significantfrank/article/details/104996419