状态模式和状态机

前言
事物具有一系列状态,并且随着状态的变更某些行为会有差异,这样的代码中就会充斥中状态的if判断,解耦这些不同状态下的行为将是一件非常重要的设计。
我们试想下如何解决这种问题,首先想到的是策略模式:每种状态可以对应一个策略,策略中包含着对应状态的行为,然后我们还可以考虑采用命令模式,具体的行为放到命令处理器中执行,本文将探讨与状态这个概念紧密相连的设计模式:状态模式,利用它来编写更优雅的代码。
状态模式-State Design Pattern
状态模式是一种行为模式,它将状态和行为解耦,当一个类的状态改变时,它的行为也随之改变。

从类图中可以看到,状态模式是多态特性和面向接口的完美体现,State是一个接口,表示状态的抽象,ConcreteStateA和ConcreteStateB是具体的状态实现类,表示两种状态的行为,Context的request()方法将会根据状态的变更从而调用不同State接口实现类的具体行为方法,具体用代码实现状态模式时,可以参考这篇文章:https://github.com/iluwatar/java-design-patterns/tree/master/state。
有一个很关键的点就是State的实现类到底需不需要依赖Context?典型状态模式的代码实现中是依赖了Context,目的是在每个状态的行为内部,调用Context的状态变更方法。个人认为状态实现类可以不依赖Context,我们可以将状态改变的触发动作从状态实现类解耦出去,状态机(StateMachine)通过一种类似事件的机制实现这种解耦。
有限状态机-Finite State Machine

有限状态机是一种数学计算模型,在任何时刻,状态机都处于有限状态中的某一个状态,它可以响应输入事件,从而执行一个动作或者转换到另一个状态,这个过程称为过渡(transition),我们先来认识几个重要的术语:
1. State 状态
有限状态机的状态是一个固定的集合,状态的变迁可以通过状态迁移图来表示,它可以拥有一个开始状态和终止状态,也可以设置其初始化的状态。
当一个事件满足时,就有可能会触发状态的迁移。
2. Event 事件
事件是用来触发状态的迁移或者执行一个动作而不迁移状态。
3. Action 动作
动作是执行一段动作,这里有特殊的两个动作,就是每个状态进入或者退出时的动作:entry action 和 exit action。
4. Transition 过渡
事件触发一个状态迁移到另一个状态或者执行一个动作称之为过渡。
5. Guard 门卫
门卫根据一个boolean值用来评估当前执行状态变更或者执行动作的事件是否满足条件。
状态机的开源实现
接下来介绍几款开源的状态机,就目前落地来说Spring状态机应该是最广泛的。
Spring StateMachine
Spring状态机提供了很多落地需要考虑的支持,如StateMachineService支持状态机实例的获取和释放,StateMachineListener支持状态机的监听,StateMachinePersister支持状态机的持久化,状态机的实例化提供了Springboot的配置注解、Builder方式和工厂Factory方式,我们直接看官网的示例:
static enum States {
STATE1, STATE2
}
static enum Events {
EVENT1, EVENT2
}
public StateMachine<States, Events> buildMachine() throws Exception {
Builder<States, Events> builder = StateMachineBuilder.builder();
// 构建状态
builder.configureStates()
.withStates()
.initial(States.STATE1)
.states(EnumSet.allOf(States.class));
// 配置过渡
builder.configureTransitions()
.withExternal()
.source(States.STATE1).target(States.STATE2)
.event(Events.EVENT1)
.and()
.withExternal()
.source(States.STATE2).target(States.STATE1)
.event(Events.EVENT2);
return builder.build();
}
// demo
StateMachine<States, Events> stateMachine = buildMachine();
stateMachine.start();
stateMachine.sendEvent(Events.EVENT1);
stateMachine.sendEvent(Events.EVENT2);
Squirrel State Machine
正如官网描述的那样,squirrel状态机是一个轻量级的优雅实现。
Just like the squirrel, a small, agile, smart, alert and cute animal, squirrel-foundation is aimed to provide a lightweight, highly flexible and extensible, diagnosable, easy use and type safe Java state machine implementation for enterprise usage.
我们直接看代码:
public class QuickStartSample {
// 1. Define State Machine Event
enum FSMEvent {
ToA, ToB, ToC, ToD
}
// 2. Define State Machine Class
@StateMachineParameters(stateType=String.class, eventType=FSMEvent.class, contextType=Integer.class)
static class StateMachineSample extends AbstractUntypedStateMachine {
protected void fromAToB(String from, String to, FSMEvent event, Integer context) {
System.out.println("Transition from '"+from+"' to '"+to+"' on event '"+event+
"' with context '"+context+"'.");
}
protected void ontoB(String from, String to, FSMEvent event, Integer context) {
System.out.println("Entry State \'"+to+"\'.");
}
}
public static void main(String[] args) {
// 3. Build State Transitions
UntypedStateMachineBuilder builder = StateMachineBuilderFactory.create(StateMachineSample.class);
builder.externalTransition().from("A").to("B").on(FSMEvent.ToB).callMethod("fromAToB");
builder.onEntry("B").callMethod("ontoB");
// 4. Use State Machine
UntypedStateMachine fsm = builder.newStateMachine("A");
fsm.fire(FSMEvent.ToB, 10);
System.out.println("Current state is "+fsm.getCurrentState());
}
}
stateless4j
看代码:
StateMachineConfig<State, Trigger> phoneCallConfig = new StateMachineConfig<>();
phoneCallConfig.configure(State.OffHook)
.permit(Trigger.CallDialed, State.Ringing);
phoneCallConfig.configure(State.Ringing)
.permit(Trigger.HungUp, State.OffHook)
.permit(Trigger.CallConnected, State.Connected);
// this example uses Java 8 method references
// a Java 7 example is provided in /examples
phoneCallConfig.configure(State.Connected)
.onEntry(this::startCallTimer)
.onExit(this::stopCallTimer)
.permit(Trigger.LeftMessage, State.OffHook)
.permit(Trigger.HungUp, State.OffHook)
.permit(Trigger.PlacedOnHold, State.OnHold);
// ...
StateMachine<State, Trigger> phoneCall =
new StateMachine<>(State.OffHook, phoneCallConfig);
phoneCall.fire(Trigger.CallDialed);
assertEquals(State.Ringing, phoneCall.getState());
StateMachine
这是一个Kotlin版本的实现,之所以放上来是因为我觉得它的实现很简单,当然它的功能也简单,整个设计只有一个文件:StateMachine.kt。
自己实现状态机
通过前文的铺垫,接下来我们就可以自己实现一个状态机了,整个代码花了几个小时时间,总共2个数据结构类,1个核心状态机类,3个构建器类,有些集合的处理代码还有待优化,不过这个不影响我们看出其中的实现思路。我们以测试驱动开发的方式来介绍如何实现一个状态机,先看单元测试代码:
package com.deepoove.statemachine;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Test;
import com.deepoove.statemachine.StateMachine;
import com.deepoove.statemachine.StateMachineBuilder;
public class StateMachineTest {
private enum MyState {
STATE1, STATE2, STATE3
}
private enum MyTrigger {
TRIGGER1TO2, TRIGGER2TO3, TRIGGER3TO1, TRIGGERACTION
}
@Test
public void testSM() {
// 构建状态机
StateMachineBuilder<MyState, MyTrigger> builder = StateMachineBuilder.builder();
builder.initialState(MyState.STATE1);
builder.state(MyState.STATE1).on(MyTrigger.TRIGGER1TO2).transitionToState(MyState.STATE2)
.action((context) -> {});
builder.state(MyState.STATE2).on(MyTrigger.TRIGGER2TO3).transitionToState(MyState.STATE3);
builder.state(MyState.STATE2).on(MyTrigger.TRIGGERACTION).action((context) -> {});
builder.state(MyState.STATE2).onEntry((context) -> {}).onExit((context) -> {});
builder.state(MyState.STATE3).on(MyTrigger.TRIGGER3TO1).transitionToState(MyState.STATE1);
StateMachine<MyState, MyTrigger> sm = builder.build();
assertThat(sm.getState(), equalTo(MyState.STATE1));
// 事件触发
sm.fire(MyTrigger.TRIGGER1TO2);
assertThat(sm.getState(), equalTo(MyState.STATE2));
}
@Test
public void testSMForString() {
StateMachineBuilder<String, String> builder = StateMachineBuilder.builder();
builder.initialState("A");
builder.state("A").on("tob").transitionToState("B").action((context) -> {System.out.println("has b");});
builder.state("B").on("toc").transitionToState("C");
builder.state("B").on("toaction").action((context) -> {});
builder.state("B").onEntry((context) -> {System.out.println("entry b");}).onExit((context) -> {});
builder.state("C").on("toa").transitionToState("A");
StateMachine<String, String> sm = builder.build();
assertThat(sm.getState(), equalTo("A"));
sm.fire("tob");
assertThat(sm.getState(), equalTo("B"));
}
}
状态机的基本功能都有了,整个测试代码的核心分为两块,一是构建状态机,一是触发事件执行状态机预设的迁移和动作。
第一步:定义状态机数据结构StateAction<S, T>
核心类负责存储状态机的预设逻辑,它的核心方法是fire触发事件。因为状态机的预设逻辑是根据某个现态走的,数据结构就是每一个状态映射一系列过渡,而每个过渡是由事件触发,所以每个状态内维护了一系列事件和过渡的映射,每个状态进入和离开的动作也将在这里面定义,我们先看看这种数据结构:
package com.deepoove.statemachine;
import java.util.Map;
import java.util.function.Consumer;
public class StateAction<S, T> {
// 状态
S state;
Consumer<StateMachineContext> entry;
Consumer<StateMachineContext> exit;
// 一系列事件和过渡的映射
Map<T, Transition<S>> transitions;
public S getState() {
return state;
}
public void entry(StateMachineContext context) {
if (null != entry) entry.accept(context);
}
public void exit(StateMachineContext context) {
if (null != exit) exit.accept(context);
}
public Transition<S> getTransition(T trigger) {
return transitions.get(trigger);
}
}
过渡类Transition<S>维护了次态或者需要执行的动作:
package com.deepoove.statemachine;
import java.util.function.Consumer;
public class Transition<S> {
S toState;
Consumer<StateMachineContext> action;
public S getToState() {
return toState;
}
public void action(StateMachineContext context) {
if (null != action) action.accept(context);
}
}
第二步:定义状态机StateMachine<S, T>
状态机的核心方法是触发事件方法:fire(),它整合了某种事件下状态的迁移和动作的触发,其中StateMachineContext是一个上下文类,这里就没有实现了。
package com.deepoove.statemachine;
import java.util.List;
import java.util.stream.Collectors;
public class StateMachine<S, T> {
S currentState;
List<StateAction<S, T>> states;
public StateMachine() {
}
public S getState() {
return currentState;
}
public void fire(T trigger) {
StateAction<S, T> stateAction = getStateAction(currentState);
Transition<S> transition = stateAction.getTransition(trigger);
S toState = transition.getToState();
if (null == toState) {
transition.action(new StateMachineContext());
} else {
stateAction.exit(new StateMachineContext());
StateAction<S, T> toStateAction = getStateAction(toState);
toStateAction.entry(new StateMachineContext());
transition.action(new StateMachineContext());
currentState = toState;
}
}
private StateAction<S, T> getStateAction(S state) {
List<StateAction<S, T>> collect = states.stream()
.filter(stateAction -> stateAction.getState().equals(state))
.collect(Collectors.toList());
return collect.get(0);
}
}
最后一步:Builder模式实例化状态机
主要维护了三个构建器:StateMachineBuilder<S, T>、StateActionBuilder<S, T>和TransitionBuilder<S>,这些构建器的核心目的就是通过链式调用来预设状态机的逻辑。
StateMachineBuilder<S, T>的实现如下:
package com.deepoove.statemachine;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class StateMachineBuilder<S, T> {
private Map<S, StateActionBuilder<S, T>> builders = new HashMap<>();
private StateMachine<S, T> stateMachine;
public StateMachineBuilder() {
stateMachine = new StateMachine<>();
}
public static <S, T> StateMachineBuilder<S, T> builder() {
return new StateMachineBuilder<>();
}
public void initialState(S s) {
stateMachine.currentState = s;
}
public StateActionBuilder<S, T> state(S s) {
StateActionBuilder<S, T> stateActionBuilder = builders.get(s);
if (null == stateActionBuilder) {
builders.put(s, stateActionBuilder = new StateActionBuilder<S, T>(s));
}
return stateActionBuilder;
}
public StateMachine<S, T> build() {
stateMachine.states = builders.values().stream().map((stateBuilder) -> stateBuilder.buid())
.collect(Collectors.toList());
return stateMachine;
}
}
StateActionBuilder<S, T>的实现如下:
package com.deepoove.statemachine;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public class StateActionBuilder<S, T> {
StateAction<S, T> stateAction;
private Map<T, TransitionBuilder<S>> builders = new HashMap<>();
public StateActionBuilder(S s) {
stateAction = new StateAction<>();
stateAction.state = s;
}
public TransitionBuilder<S> on(T trigger) {
TransitionBuilder<S> builder = builders.get(trigger);
if (null == builder) {
builders.put(trigger, builder = new TransitionBuilder<S>());
}
return builder;
}
public StateActionBuilder<S, T> onEntry(Consumer<StateMachineContext> entryAction) {
stateAction.entry = entryAction;
return this;
}
public StateActionBuilder<S, T> onExit(Consumer<StateMachineContext> exitAction) {
stateAction.exit = exitAction;
return this;
}
public StateAction<S, T> buid() {
Map<T, Transition<S>> map = new HashMap<>();
builders.forEach((t, builder) -> {
map.put(t, builder.build());
});
stateAction.transitions = map;
return stateAction;
}
}
TransitionBuilder<S>的实现如下:
package com.deepoove.statemachine;
import java.util.function.Consumer;
public class TransitionBuilder<S> {
Transition<S> transition;
public TransitionBuilder() {
transition = new Transition<>();
}
public TransitionBuilder<S> transitionToState(S s) {
transition.toState = s;
return this;
}
public void action(Consumer<StateMachineContext> action) {
transition.action = action;
}
public Transition<S> build() {
return transition;
}
}
总结
状态模式清晰的描述了状态和行为的关系,将不同状态的行为进行解耦,我们可以从整体上掌握所有状态的迁移和行为。
让业务代码变得更好维护,用更贴切的设计描述业务才是每个业务开发者的价值。