sayi.github.com icon indicating copy to clipboard operation
sayi.github.com copied to clipboard

状态模式和状态机

Open Sayi opened this issue 6 years ago • 0 comments

image

前言

事物具有一系列状态,并且随着状态的变更某些行为会有差异,这样的代码中就会充斥中状态的if判断,解耦这些不同状态下的行为将是一件非常重要的设计。

我们试想下如何解决这种问题,首先想到的是策略模式:每种状态可以对应一个策略,策略中包含着对应状态的行为,然后我们还可以考虑采用命令模式,具体的行为放到命令处理器中执行,本文将探讨与状态这个概念紧密相连的设计模式:状态模式,利用它来编写更优雅的代码。

状态模式-State Design Pattern

状态模式是一种行为模式,它将状态和行为解耦,当一个类的状态改变时,它的行为也随之改变。

来自维基百科

从类图中可以看到,状态模式是多态特性和面向接口的完美体现,State是一个接口,表示状态的抽象,ConcreteStateAConcreteStateB是具体的状态实现类,表示两种状态的行为,Contextrequest()方法将会根据状态的变更从而调用不同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;
    }

}

总结

状态模式清晰的描述了状态和行为的关系,将不同状态的行为进行解耦,我们可以从整体上掌握所有状态的迁移和行为。

让业务代码变得更好维护,用更贴切的设计描述业务才是每个业务开发者的价值

Sayi avatar Apr 08 '19 03:04 Sayi