有限状态机FSM的几种简单实现

『代码github地址』

标签: 有限状态机,Akka fsm,squirrel-foundation,java状态模式、责任链模式


1. 有限状态机的概念

有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

通常FSM包含几个要素:状态的管理、状态的监控、状态的触发、状态触发后引发的动作。

这些关系的意思可以这样理解:

  • State(S) x Event(E) -> Actions (A), State(S’)
  • 如果我们当前处于状态S,发生了E事件, 我们应执行操作A,然后将状态转换为S’

下面展示最常见的表示:当前状态(B)和条件(Y)的组合指示出下一个状态(C)。完整的动作信息可以只使用脚注来增加。包括完整动作信息的FSM定义可以使用状态表。

条件↓当前状态→ 状态A 状态B 状态C
条件X
条件Y 状态C
条件Z

2. 使用状态机的应用背景

在广告投放项目中由于复杂的广告投放逻辑,存在着大量的if-else 判断类似的硬编码,希望能借助对fsm模型的调研,找出理想的实现方式。

3.有限状态机的几种实现

3.1枚举类实现java状态模式

首先在枚举类中 定义state 和定义的抽象方法。

public enum JavaPlatformState {
    //  定义state
    OPEN{
        @Override void exit(JavaPlatformMachine pm){super.exit(pm);}
        
        @Override void valid(JavaPlatformMachine pm){
            this.exit(pm);
            if(pm.data.getValid_()){
                pm.state =STEP1;
            }else{
                NotFound();
                pm.state =OFF;
            }
            pm.state.entry(pm);
        }

        @Override
        void first(JavaPlatformMachine pm) {}

        @Override
        void businessLine(JavaPlatformMachine pm) {}

        @Override
        void district(JavaPlatformMachine pm) {}
    },
    STEP1{
        @Override void exit(JavaPlatformMachine pm){super.exit(pm);}

        @Override
        void valid(JavaPlatformMachine pm) {}

        @Override void first(JavaPlatformMachine pm){
            this.exit(pm);
            if(!pm.data.getFirst_()){
                pm.state =STEP2;
            }else{
                ReturnDimension();
                pm.state =OFF;
            }
            pm.state.entry(pm);
        }

        @Override
        void businessLine(JavaPlatformMachine pm) {}

        @Override
        void district(JavaPlatformMachine pm) {}
    },
    ...
   
    //状态模式 提取的接口  在常量实体类中实现抽象方法
    abstract void valid(JavaPlatformMachine pm);
    abstract void first(JavaPlatformMachine pm);
    abstract void businessLine(JavaPlatformMachine pm);
    abstract void district(JavaPlatformMachine pm); 
}      

在enum JavaPlatformState 中,除了状态模式 提取的接口外,添加了状态机的各种动作action实现

//状态机的各种动作action methode
    void entry(JavaPlatformMachine pm){System.out.println("→"+pm.state.name());}
    void exit(JavaPlatformMachine pm){System.out.println(pm.state.name()+"→ ");}
    
    void NotFound(){System.out.println("NotFound");}
    void ReturnDimension(){System.out.println("ReturnDimension");}
    void PreciseAdvertising(){System.out.println("PreciseAdvertising");}
    void Top9(){System.out.println("Top9");}

建立状态机实体,ContextData是封装条件的bean类,初始化状态OPEN,在状态机里定义action,调用对应state的相应的方法。

public class ContextData {
    private Boolean isValid_;//广告位是否有效
    private Boolean isFirst_;//是否第一次请求
    private Boolean isBusinessLine_;//是否属于业务线广告位
    private Boolean district_;//是否有地域
    ...
}    

public class JavaPlatformMachine {
    ContextData data = new ContextData();
    JavaPlatformState state = JavaPlatformState.OPEN;
    //Action
    public void valid(){state.valid(this);}
    public void first(){state.first(this);}
    public void businessLine(){state.businessLine(this);}
    public void district(){state.district(this);}
}

测试方法,初始化状态机,设置参数,按次序调用对应的Action

    JavaPlatformMachine pm = new JavaPlatformMachine();
    pm.data.setValid_(true);// 广告位是否有效
    pm.data.setFirst_(false);// 是否第一次请求
    pm.data.setBusinessLine_(true);//是否属于业务线广告位
    pm.data.setDistrict_(true);//是否有地域
    pm.valid();
    pm.first();
    pm.businessLine();
    pm.district();

输出结果

OPEN→ 
→STEP1
STEP1→ 
→STEP2
STEP2→ 
→STEP3
STEP3→ 
Top9
→OFF

在设置参数下,最后状态调用Top9。
不过这种方式在枚举类中实现抽象方法,每个state下都要覆写(Override)action的抽象方法,显然不利于拓展。

参考资料:
http://blog.csdn.net/yqj2065/article/details/39371487

3.2抽象类实现java状态模式

当一个类的某个成员变量的值变化时,可能导致多个行为表现得不同。将该成员变量封装成类型的模式,即为状态模式(state pattern)。即用多态来重构分支结构。

首先抽象状态类,定义一个接口以封装与Context的一个特定状态相关的行为

public abstract class State {
    public abstract void Handle(StateModeContext context );
    public abstract boolean isFinalflag();
}

Context类,维护一个State子类的实例,这个实例定义当前的状态。

public class StateModeContext
{
    private State state;
    private ContextData data ;

    public ContextData getData() {
        return data;
    }

    public void setData(ContextData data) {
        this.data = data;
    }

    public State getState() {
        return state;
    }

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

    /// 定义Context的初始状态
    public StateModeContext(State state , ContextData data )
    {
        this.data = data;
        this.state = state;
    }

    /// 对请求做处理,并设置下一个状态
    boolean  trueFlag = true;
    
    public void Request()
    {
        //如果当前step 是最后一步  nextStep 不执行
        if(state.isFinalflag()){
            trueFlag = false;
        }
        state.Handle(this);
    }
}

最后定义各个状态子类

public class State404 extends State {
    @Override
    public void Handle(StateModeContext context) {
        System.out.println("当前状态是 404  do something");
    }
    @Override
    public boolean isFinalflag() {
        return true;
    }
}

其中设置一个FinalFlag 标识 是否是最终状态。
在下面state的子类里面进行条件判断,并设置下一个状态,这一点有点类似责任链模式(在抽象类中定义一个实例成员变量 next handler 的引用)

public class StateStep1 extends State {
    @Override
    public void Handle(StateModeContext context) {
        System.out.println("当前状态是 step1");
        ContextData data = context.getData();
        if(data.getFirst_()){
            System.out.println("step1 -> dimension(返回尺寸)");
            context.setState(new StateDimension());
        }else{
            System.out.println("step1 -> step2");
            context.setState(new StateStep2());
        }
    }
    @Override
    public boolean isFinalflag() {
        return false;
    }
}

测试类:设置初始化数据和初始化状态stateOpen,根据状态树的深度确定循环迭代次数,进行迭代。

public class StateModeTest {
    public static void main(String[] args) {
        // 设置Context的初始状态为ConcreteStateA
        ContextData data = new ContextData(true,false,true,true);
        StateModeContext context = new StateModeContext(new StateOpen(),data);
        // 不断地进行请求,同时更改状态
        int size = 4;// 请求迭代数
        for(int i = 0 ; i< size+1; i++){
            if(context.trueFlag){
                context.Request();
            }
        }
    }
}

输出结果:

当前状态是 open
open -> step1
当前状态是 step1
step1 -> step2
当前状态是 step2
step2 -> step3
当前状态是 step3
step3 -> Top9(返回9素材)
当前状态是 Top9(返回9素材)  do something

这种方式实现状态模式,每个状态要new一个状态的子类,而且手动指定循环迭代次数通过迭代方式进行事件的调用。

3.3 Akka FSM 实现状态机

Akka的状态机是非常简洁的实现,充分利用了Scala的许多先进的语法糖让代码更加简洁清晰。是基于Akka Actor 实现,封装了很多自定义的API(实际上就是DSL)。

在底层,Akka FSM就是一个继承了Actor的trait(scala的特征trait就相当于java的接口)

trait FSM[S, D] extends Actor with Listeners with ActorLogging { ...}

FSM trait提供了一个包装了常规Actor的DSL,让我们能集中注意力在更快的构建手头的状态机上。常规Actor只有一个receive方法,FSM trait包装了receive方法的实现并将调用指向到一个特定状态机的处理代码块。

class PlatformMachine extends FSM[PlatformState,PlatformData]

上面的 PlatformMachine 是一个FSM ACTOR。
PlatformState,PlatformData分别为我定义的状态和数据。在FSM中,有两个东西是一直存在的,任何时间点都有状态 ,和在状态中进行共享的数据。 这代表所有的fsm的状态继承自PlatformState,而所有在状态间共享的数据就是PlatformData

在伴生对象中定义状态,消息,数据。

在Scala的类中,与类名相同的对象(object)叫做伴生对象,类和伴生对象之间可以相互访问私有的方法和属性。在Scala中没有静态方法和静态字段,但是可以使用object这个语法结构来达到同样的目的。object是单例模式的,一般也用于存放工具方法和常量,共享单个不可变的实例等。

在Scala中样例类(case class)是一中特殊的类,可用于模式匹配。case class是多例的,后面要跟构造参数,case object是单例的。这点其实和class与object的区别是一样的。

object PlatformMachine{
  sealed trait PlatformState
  //定义6个State
  case object Open extends PlatformState
  case object Off extends PlatformState
  case object Step1 extends PlatformState
  case object Step2 extends PlatformState
  case object Step3 extends PlatformState
  case object Step4 extends PlatformState
  // state data container
  case class PlatformData(isValid: Boolean, isFirst: Boolean, isBusinessLine: Boolean,district:Boolean)
  //定义交互消息
  sealed trait UserMessage
  case object ToHoldOn extends  UserMessage
  case object ToNotFound extends  UserMessage
  case object ToReturnDimension extends UserMessage
  case object ToPreciseAdvertising extends UserMessage
  case object ToTop9 extends UserMessage
  case class SetInitData(isValid: Boolean, isFirst: Boolean, isBusinessLine: Boolean,district:Boolean) extends UserMessage
}

以下是FSM 类的代码

class PlatformMachine extends FSM[PlatformState,PlatformData]{
  val service = new PlatformMachineService()

  startWith(Open, PlatformData(false,false,false,false))

  //Handlers of State
  when(Open){
    case Event(SetInitData(isValid_,isFirst_,isBusinessLine_,district_), _) =>{
      println(s"SetInitData:$isValid_ , $isFirst_ ,$isBusinessLine_ ,$district_  ")
      stay using stateData.copy(isValid = isValid_,isFirst=isFirst_,isBusinessLine=isBusinessLine_,district=district_)
    }

    case Event(ToNotFound, PlatformData(isValid,_,_,_))   => {
      if (isValid equals  false)  {
        println("goto NotFound!")
        service.notFound()
        goto(Off)
      }else{
        println("goto step1")
        goto(Step1)
      }
    }
  }

  when(Off){
    //接收off状态下的所有Event
    case _ => {
      println("end !")
      stay()
    }
  }

  when(Step1){
    case Event(ToReturnDimension, PlatformData(_,isFirst,_,_))   => {
      //是否第一次请求 /是  返回广告位大小尺寸数据
      if (isFirst equals  true){
        println("goto ReturnDimension!")
        service.returnDimension()
        goto(Off)
      }else{
        println("goto step2")
        goto(Step2)
      }
    }
  }

  when(Step2){
    case Event(ToPreciseAdvertising, PlatformData(_,_,isBusinessLine,_))   => {
      //是否业务线广告位 /是  返回精准投放
      if (isBusinessLine equals  false){
        println("goto PreciseAdvertising!")
        service.preciseAdvertising()
        goto(Off)
      }else{
        println("goto step3")
        goto(Step3)
      }
    }
  }

  when(Step3){
    case Event(ToTop9, PlatformData(_,_,_,district))   => {
      //是否有地域 /是  返回9素材
      if (district equals  true){
        println("goto Top9!")
        service.top9()
        goto(Off)
      }else{
        println("goto step4")
        goto(Step4)
      }
    }
  }

  when(Step4){
    //接收off状态下的所有Event
    case _ => {
      println("Step4 end !")
      stay()
    }
  }

  whenUnhandled {
    case _ => {
      goto(Open)
    }
  }

  onTransition {
    case Open  -> Step1 => println("-------------------------onTransition : from Open to Step1  ! ")
    case Open  -> Off   => println("-------------------------onTransition : from Open to OFF    ! ")
    case Step1 -> Step2 => println("-------------------------onTransition : from Step1 to Step2 ! ")
    case Step1 -> Off   => println("-------------------------onTransition : from Step1 to OFF   ! ")
    case Step2 -> Step3 => println("-------------------------onTransition : from Step2 to Step3 ! ")
    case Step2 -> Off   => println("-------------------------onTransition : from Step2 to OFF   ! ")
    case Step3 -> Step4 => println("-------------------------onTransition : from Step3 to Step4 ! ")
    case Step3 -> Off   => println("-------------------------onTransition : from Step3 to OFF   ! ")
  }

}

如上,首先是定义初始化状态为open和初始化数据。

startWith(Open, PlatformData(false,false,false,false))

接下来是状态处理器,根据各个业务来,例如:

when(Open){
    case Event(SetInitData(isValid_,isFirst_,isBusinessLine_,district_), _) =>{
      println(s"SetInitData:$isValid_ , $isFirst_ ,$isBusinessLine_ ,$district_  ")
      stay using stateData.copy(isValid = isValid_,isFirst=isFirst_,isBusinessLine=isBusinessLine_,district=district_)
    }

    case Event(ToNotFound, PlatformData(isValid,_,_,_))   => {
      if (isValid equals  false)  {
        println("goto NotFound!")
        service.notFound()
        goto(Off)
      }else{
        println("goto step1")
        goto(Step1)
      }
    }
  }

我们有一个初始状态(Open),when(open)代码块处理Open状态的
收到的消息event,ToNotFound由when(ToNotFound)代码块来处理。我提到的消息与常规我们发给Actor的消息时一样的,消息与数据一起包装过。包装后的叫做Event(akka.actor.FSM.Event),看起来的样例是这样case Event(ToNotFound, PlatformData(isValid,_,_,_)),比如我这里获得了isValid的参数,在Open的状态下模式匹配到了ToNotFound,在函数里面根据参数做业务判断,调用业务方法或者通过调用goto方法,调度到下一个状态。

还有一个event是SetInitData,我这里是设置自定义初始化数据用的,其中有几个关键字stay,usingstateData

每一个被阻塞的case都必须返回一个State。这个可以用stay来完成,含义是已经在处理这条消息的最后了,以下是stay方法的源码实现:

 final def stay(): State = goto(currentState.stateName) // cannot directly use currentState because of the timeout field

其实也就是调用了goto
using方法可以让我们把改过的数据传给下个状态。

  whenUnhandled {
    case _ => {
      goto(Open)
    }
  }

  onTransition {
    case Open  -> Step1 => println("-------------------------onTransition : from Open to Step1  ! ")
    case Open  -> Off   => println("-------------------------onTransition : from Open to OFF    ! ")
    case Step1 -> Step2 => println("-------------------------onTransition : from Step1 to Step2 ! ")
    case Step1 -> Off   => println("-------------------------onTransition : from Step1 to OFF   ! ")
    case Step2 -> Step3 => println("-------------------------onTransition : from Step2 to Step3 ! ")
    case Step2 -> Off   => println("-------------------------onTransition : from Step2 to OFF   ! ")
    case Step3 -> Step4 => println("-------------------------onTransition : from Step3 to Step4 ! ")
    case Step3 -> Off   => println("-------------------------onTransition : from Step3 to OFF   ! ")
  }

whenUnhandled 是如果没有匹配到,FSM Actor会尝试将我们的消息与whenUnhandled块中的模式进行匹配。
onTransition 是在状态变化时做出反应或得到通知。
测试类:

class PlatformSpec extends TestKit(ActorSystem("platform-system"))
  with MustMatchers  //must描述assertion,比如"hello" must (contain("hello"))
  with FunSpecLike
  with ImplicitSender {
  val begin: Long = System.currentTimeMillis()
  describe("just 4 test") {
    it("TestKit Demo") {
      val platformMachine = TestActorRef(Props(new PlatformMachine()))
      platformMachine ! SetInitData(true,false,false,true)
      platformMachine ! ToNotFound
      platformMachine ! ToReturnDimension
      platformMachine ! ToPreciseAdvertising
      platformMachine ! ToTop9
    }
  }
  val end: Long = System.currentTimeMillis()
  System.out.println("方法耗时:"+(end-begin));
}

new 一个状态机,对这个ActorRef 发送消息,按顺序执行。这里的和scala Actor 编程的模式一样,表示发送异步消息,没有返回值。
执行结果:

方法耗时:36
SetInitData:true , false ,false ,true  
goto step1
-------------------------onTransition : from Open to Step1  ! 
goto step2
-------------------------onTransition : from Step1 to Step2 ! 
goto PreciseAdvertising!
精准投放!
-------------------------onTransition : from Step2 to OFF   ! 
end !

由结果看出来,其也是异步调用的。
由于网上找的资料都是用继承Akka的TestKit测试包来进行测试的demo,现在我还没找到实际能用于生产上的解决方案。
如我下面代码:

object PlatformTest extends App{
  private val begin: Long = System.currentTimeMillis()
  val system = ActorSystem()
  val machine: ActorRef = system.actorOf(Props[PlatformMachine],"plantformTest")
  machine ! SetInitData(true,false,true,true)
  machine ! ToNotFound
  machine ! ToReturnDimension
  machine ! ToPreciseAdvertising
  machine ! ToTop9
  Thread.sleep(100)
  system.shutdown()
  private val end: Long = System.currentTimeMillis()
  System.out.println("方法耗时:"+(end-begin));
//  system.awaitTermination()
}

其测试结果:

SetInitData:true , false ,true ,true  
goto step1
-------------------------onTransition : from Open to Step1  ! 
goto step2
-------------------------onTransition : from Step1 to Step2 ! 
goto step3
-------------------------onTransition : from Step2 to Step3 ! 
goto Top9!
返回9素材!
-------------------------onTransition : from Step3 to OFF   ! 
方法耗时:638

通过ActorSystem调用actorOf的方式 获得machine这个ActorRef,进行异步发送消息,我这里是先线程sleep 再关闭ActorSystem。这块我用的比较浅,还没有找到其他更好的方法。

参考资料:
http://udn.yyuap.com/doc/akka-doc-cn/2.3.6/scala/book/chapter3/07_fsm.html
http://www.jianshu.com/p/41905206b3b3
http://www.cnphp6.com/archives/29029

3.4 squirrel state machine 实现状态机

squirrel-foundation是一款轻量级的java有限状态机。既支持流式API又支持声明式创建状态机,允许用户以一种简单方式定义操作方法。这里只介绍状态机squirrel的初级用法。

3.4.1简单操作介绍

state machine(T), state(S), event(E) and context(C)

  • T代表实现的状态机类型。
  • S代表实现的状态类型。
  • E代表实现的事件类型。
  • C代表实现的外部上下文类型。

首先得先创建一个状态机

  • 通过StateMachineBuilderFactory创建的StateMachineBuilder用来定义状态机。
  • 所有的状态机实例会被同一个状态机builder创建,该builder共享一份结构化的数据,从而优化内存的使用。
  • 状态机builder在生成状态机的时候使用lazy模式。当builder创建第一个状态机实例时,包含时间消耗的状态机定义才会被创建。但是状态机定义生成之后,接下来的状态机创建将会非常快。状态机builder应该尽量重用。
UntypedStateMachineBuilder builder = StateMachineBuilderFactory.create(FSMController.class);

状态机builder创建之后,定义状态机的state,transition和action,执行external Transition。

builder.externalTransition().from(MyState.C).to(MyState.D).on(MyEvent.GoToD).when(
    new Condition<MyContext>() {
        @Override
        public boolean isSatisfied(MyContext context) {
            return context!=null && context.getValue()>80;
        }

        @Override
        public String name() {
            return "MyCondition";
        }
}).callMethod("thisMethod");

也可以使用流式API来定义状态机。

builder.externalTransition().from(MyState.C).to(MyState.D).on(MyEvent.GoToD)
.whenMvel("MyCondition:::(context!=null &&context.getValue()>80)")
.callMethod("thisMethod");

即从MyState.C 到MyState.D,并且在事件MyEvent.GoToD 下触发,满足条件context!=null &&context.getValue()>80)后执行该thisMethod方法。

这里的whenMvel使用MVEL来描述条件,字符:::用来分离条件名称和条件表达式。context是预先定义好的指向当前上下文的对象。

这样的条件判断 是在MyState.C 到MyState.D 在GoToD的event 下满足MyCondition的情况下 才会CallMethod,但是这里when 只是内部条件判断,只是从from状态C to 状态D 转移中进行条件判断 判断不过则不执行状态转移。
如果我要取代if-else 条件判断的需求的话,如果按这种写法 是不是该这么写:

builder.externalTransition().from(MyState.C).to(MyState.D).on(MyEvent.GoToD)
.whenMvel("MyCondition:::(context!=null &&context.getValue()>80)")
.callMethod("thisMethod");
builder.externalTransition().from(MyState.C).to(MyState.E).on(MyEvent.GoToE)
.whenMvel("MyCondition:::(context!=null &&context.getValue()=<80)")
.callMethod("OtherMethod");

这样子肯定不行,其实还有API,可以一次定义多个transition,如下:

builder.transitions().from(MyState.C).toAmong(MyState.D, MyState.E)
.onEach(MyEvent.GoToD, MyEvent.GoToE).callMethod("thisMethod|OtherMethod");

不过这样子,首先在之前就要定义条件才行

public class FSMDecisionMaker extends UntypedAnonymousAction {
    @Override
    public void execute(Object from, Object to, Object event, Object context, UntypedStateMachine stateMachine) {
        MyState typedTo = (MyState)to;
        FSMContextData typedContext = (FSMContextData)context;
        if(typedTo == MyState.C){
            if(typedContext.getValue()>80) {
                stateMachine.fire(MyEvent.GoToD, context);
            } else {
                stateMachine.fire(MyEvent.GoToE, context);
            }
        }
}

定义完这个继承隐式匿名action的类,并在里面根据ContextData写业务跳转逻辑。stateMachine.fire(MyEvent.GoToD, context);用户可以发送event以及context,在状态机内部触发transition。
如下代码:接下来在测试类里面实例化这个对象,并且定义在状态C入口时定义这个action,这样每次在进入状态C的时候就会执行FSMDecisionMaker里面的execute方法了。可以配合该方法参数列表内的 Object to (即transition target state) 进行各个状态的条件判断了。

FSMDecisionMaker decisionMaker = new FSMDecisionMaker("DecisionMaker");
builder.onEntry(MyState.C).perform(decisionMaker);

squirrel-foundation还提供注解方式来定义和扩展状态机。例子如下:

@States({
    @State(name="A", entryCallMethod="entryStateA", exitCallMethod="exitStateA"), 
    @State(name="B", entryCallMethod="entryStateB", exitCallMethod="exitStateB")
})
@Transitions({
    @Transit(from="A", to="B", on="GoToB", callMethod="stateAToStateBOnGotoB"),
    @Transit(from="A", to="A", on="WithinA", callMethod="stateAToStateAOnWithinA", type=TransitionType.INTERNAL)
})
interface MyStateMachine extends StateMachine<MyStateMachine, MyState, MyEvent, MyContext> {
    void entryStateA(MyState from, MyState to, MyEvent event, MyContext context);
    void stateAToStateBOnGotoB(MyState from, MyState to, MyEvent event, MyContext context)
    void stateAToStateAOnWithinA(MyState from, MyState to, MyEvent event, MyContext context)
    void exitStateA(MyState from, MyState to, MyEvent event, MyContext context);
    ...
}

注解既可以定义在状态机的实现类上也可以定义在状态机需要实现的任何接口上面,流式API定义的状态机也可以使用注解(但是接口中定义的方法必须要是public的)。

在接口或者实现类里面注册相应的method,在注解里面定义entry 或者 exit 该state调用的method,和对应event 触发Transitions 所调用的method。

3.4.2 根据业务编写Demo

状态流程图如下(ps:简书的markdown 太坑,还没有画流程图的功能,文字也不能高亮,下面只有流程图markdown的原文,还是能看明白的...):

graph TB
    Open(Open)-->Start(Start)
    Start --> a{isValid<br/>广告位是否有效}
    a --> |N|NotFound(NotFound<br/>404)
    a --> |Y|Step1(Step1)
    Step1 --> b{isPrivateAD<br/>是否私有广告位}
    b --> |N|PublicDelivery(PublicDelivery<br/>公有投放)
    b --> |Y|Step2(Step2)
    PublicDelivery-->MeteriaMatchLogicDiagram(MeteriaMatchLogicDiagram<br/>物料匹配逻辑图)
    Step2 --> c{isPrivateAdvertiser<br/>是否私有广告主}
    c --> |N|MeteriaMatchLogic3(MeteriaMatchLogic3<br/>物料匹配逻辑)
    c --> |Y|Step3(Step3)
    Step3 --> d{isFixedDelivery<br/>广告位是否有定投广告计划}
    d --> |Y|MeteriaMatchLogic1(MeteriaMatchLogic1<br/>物料匹配逻辑)
    d --> |N|Step4(Step4)
    MeteriaMatchLogic1 --> |isNoMaterialDelivery<br/>没有素材可投|Step4
    Step4 --> e{isUnFixedPmp<br/>是否有非定投PMP广告计划}
    e --> |Y|MeteriaMatchLogic2(MeteriaMatchLogic2<br/>物料匹配逻辑)
    e --> |N|Step5(Step5)
    MeteriaMatchLogic2 --> |isNoMaterialDelivery<br/>没有素材可投|Step5
    Step5 --> f{isUnFixedUnPmp<br/>是否有非定投非PMP广告计划}
    f --> |Y|MeteriaMatchLogic3
    f --> |N|PublicDelivery
  1. 首先定义context 上下文数据对象FSMContextData ,作为StateMachineParameters 里的contextType。
public class FSMContextData {
    private Boolean valid;//广告位是否有效
    private Boolean privateAD;//是否私有广告位
    private Boolean privateAdvertiser;//是否私有广告主
    private Boolean fixedDelivery;//广告位是否有定投广告计划
    private Boolean unFixedPmp;//是否有非定投PMP广告计划
    private Boolean unFixedUnPmp;//是否有非定投非PMP广告计划
    private Boolean noMaterialDelivery;//是否没有素材可投
    ...
    Getter and Construstor with Fields...
    }
  1. 其次定义一个FSMController类 ,其继承AbstractUntypedStateMachine,实际上就抽象成了一个状态机,里面我注册了event,state,还有action等。
@Transitions({
        @Transit(from="Open", to="Start", on="ToStart"),
        @Transit(from="PublicDelivery", to="MeteriaMatchLogicDiagram", on="ToMeteriaMatchLogicDiagram"),
        @Transit(from="MeteriaMatchLogic1", to="Step4", on="ToStep4",whenMvel="MyCondition:::(context!=null && context.isNoMaterialDelivery())"),
        @Transit(from="MeteriaMatchLogic2", to="Step5", on="ToStep5",whenMvel="MyCondition:::(context!=null && context.isNoMaterialDelivery())"),
})
@States({
        @State(name="Start", entryCallMethod="ontoStart"),
        @State(name="Step1", entryCallMethod="ontoStep1"),
        @State(name="Step2", entryCallMethod="ontoStep2"),
        @State(name="Step3", entryCallMethod="ontoStep3"),
        @State(name="Step4", entryCallMethod="ontoStep4"),
        @State(name="Step5", entryCallMethod="ontoStep5"),
        @State(name="NotFound", entryCallMethod="ontoNotFound"),
        @State(name="PublicDelivery", entryCallMethod="ontoPublicDelivery"),
        @State(name="MeteriaMatchLogicDiagram", entryCallMethod="ontoMeteriaMatchLogicDiagram"),
        @State(name="MeteriaMatchLogic1", entryCallMethod="ontoMeteriaMatchLogic1"),
        @State(name="MeteriaMatchLogic2", entryCallMethod="ontoMeteriaMatchLogic2"),
        @State(name="MeteriaMatchLogic3", entryCallMethod="ontoMeteriaMatchLogic3")
})
@StateMachineParameters(stateType=FSMState.class, eventType=FSMEvent.class, contextType=FSMContextData.class)
public  class FSMController extends AbstractUntypedStateMachine {

    public enum FSMEvent {
        ToStart,ToStep1,ToStep2,ToStep3,ToStep4,ToStep5,
        ToNotFound, ToPublicDelivery,ToMeteriaMatchLogicDiagram,
        ToMeteriaMatchLogic1,ToMeteriaMatchLogic2,ToMeteriaMatchLogic3
    }
    public enum FSMState {
        Open,Start,Step1,Step2,Step3,Step4,Step5,
        NotFound,PublicDelivery,MeteriaMatchLogicDiagram,
        MeteriaMatchLogic1,MeteriaMatchLogic2,MeteriaMatchLogic3
    }

    protected void ontoStart(FSMState from, FSMState to, FSMEvent event, FSMContextData context) {
        System.out.println("进入"+to+".");
    }
    protected void ontoStep1(FSMState from, FSMState to, FSMEvent event, FSMContextData context) {
        System.out.println("进入"+to+".");
    }
    protected void ontoStep2(FSMState from, FSMState to, FSMEvent event, FSMContextData context) {
        System.out.println("进入"+to+".");
    }
   ...
   
    protected void ontoNotFound(FSMState from, FSMState to, FSMEvent event, FSMContextData context) {
        System.out.println("进入"+to+".");
    }
    protected void ontoPublicDelivery(FSMState from, FSMState to, FSMEvent event, FSMContextData context) {
        System.out.println("进入"+to+".");
    }
    ...
}

注解@StateMachineParameters用来声明状态机泛型参数类型,包括stateType、eventType、contextType。AbstractUntypedStateMachine是任何无状态的状态机的基类。 在这个类里面我用枚举类定义了state 和 event,和执行的方法即action。
在该类上方用注解方式@Transit@State,可以理解为将event 或 state 和 action 做了一次映射。whenMvel条件判断一样也可以用在注解里面实现。

  1. 然后创建一个决策类FSMDecisionMaker,在该类里定义业务条件,其继承UntypedAnonymousAction ,并在里面根据typedTo区分在哪个state下,再根据 ContextData写业务跳转逻辑,用stateMachine.fire方法进行状态跳转。
public class FSMDecisionMaker extends UntypedAnonymousAction {
    final String name;
    FSMDecisionMaker(String name) {
        this.name = name;
    }
    @Override
    public String name() {
        return name;
    }
    @Override
    public void execute(Object from, Object to, Object event, Object context, UntypedStateMachine stateMachine) {
        FSMState typedTo = (FSMState)to;
        FSMContextData typedContext = (FSMContextData)context;

        if(typedTo == FSMState.Start){
            if(typedContext.isValid()) {//广告位是否有效
                stateMachine.fire(FSMEvent.ToStep1, context);//有效:step1
            } else {
                stateMachine.fire(FSMEvent.ToNotFound, context);//无效:404
            }
        }
        else if(typedTo == FSMState.Step1){
            if(typedContext.isPrivateAD()) {//是否私有广告位
                stateMachine.fire(FSMEvent.ToStep2, context);//是:step2
            } else {
                stateMachine.fire(FSMEvent.ToPublicDelivery, context);//否:公有投放
            }
        }
        else if(typedTo == FSMState.Step2){
            if(typedContext.isPrivateAdvertiser()) {//是否私有广告主
                stateMachine.fire(FSMEvent.ToStep3, context);//是:step3
            } else {
                stateMachine.fire(FSMEvent.ToMeteriaMatchLogic3, context);//否:物料匹配逻辑3
            }
        }
        else if(typedTo == FSMState.Step3){
            if(typedContext.isFixedDelivery()) {//广告位是否有定投广告计划
                stateMachine.fire(FSMEvent.ToMeteriaMatchLogic1, context);//是:物料匹配逻辑1
            } else {
                stateMachine.fire(FSMEvent.ToStep4, context);//否:step4
            }
        }
        else if(typedTo == FSMState.Step4){
            if(typedContext.isUnFixedPmp()) {//是否有非定投PMP广告计划
                stateMachine.fire(FSMEvent.ToMeteriaMatchLogic2, context);//是:物料匹配逻辑2
            } else {
                stateMachine.fire(FSMEvent.ToStep5, context);//否:step5
            }
        }
        else if(typedTo == FSMState.Step5){
            if(typedContext.isUnFixedUnPmp()) {//是否有非定投非PMP广告计划
                stateMachine.fire(FSMEvent.ToMeteriaMatchLogic3, context);//是:物料匹配逻辑3
            } else {
                stateMachine.fire(FSMEvent.ToPublicDelivery, context);//否:公有投放
            }
        }

    }
}
  1. 最后建立测试类
public class QuickStartTest {
    public static void main(String[] args) {
        long begin=System.currentTimeMillis();
        FSMContextData contextData = new FSMContextData(true,true,true,false,true,false,false);
        FSMDecisionMaker decisionMaker = new FSMDecisionMaker("DecisionMaker");
        //Build State Transitions
        UntypedStateMachineBuilder builder = StateMachineBuilderFactory.create(FSMController.class);

        // Start -> Step1 ; Start -> NotFound
        builder.onEntry(FSMController.FSMState.Start).perform(decisionMaker);
        builder.transitions().from(FSMController.FSMState.Start).toAmong(FSMController.FSMState.NotFound, FSMController.FSMState.Step1)
                .onEach(FSMController.FSMEvent.ToNotFound, FSMController.FSMEvent.ToStep1);
        // Step1 -> Step2 ; Step1 -> PublicDelivery
        builder.onEntry(FSMController.FSMState.Step1).perform(decisionMaker);
        builder.transitions().from(FSMController.FSMState.Step1).toAmong(FSMController.FSMState.PublicDelivery, FSMController.FSMState.Step2)
                .onEach(FSMController.FSMEvent.ToPublicDelivery, FSMController.FSMEvent.ToStep2);

        // Step2 -> Step3 ; Step2 -> MeteriaMatchLogic3
        builder.onEntry(FSMController.FSMState.Step2).perform(decisionMaker);
        builder.transitions().from(FSMController.FSMState.Step2).toAmong(FSMController.FSMState.MeteriaMatchLogic3, FSMController.FSMState.Step3)
                .onEach(FSMController.FSMEvent.ToMeteriaMatchLogic3, FSMController.FSMEvent.ToStep3);
        // Step3 -> Step4 ; Step3 -> MeteriaMatchLogic1
        builder.onEntry(FSMController.FSMState.Step3).perform(decisionMaker);
        builder.transitions().from(FSMController.FSMState.Step3).toAmong(FSMController.FSMState.MeteriaMatchLogic1, FSMController.FSMState.Step4)
                .onEach(FSMController.FSMEvent.ToMeteriaMatchLogic1, FSMController.FSMEvent.ToStep4);

        // Step4 -> Step5 ; Step4 -> MeteriaMatchLogic2
        builder.onEntry(FSMController.FSMState.Step4).perform(decisionMaker);
        builder.transitions().from(FSMController.FSMState.Step4).toAmong(FSMController.FSMState.MeteriaMatchLogic2, FSMController.FSMState.Step5)
                .onEach(FSMController.FSMEvent.ToMeteriaMatchLogic2, FSMController.FSMEvent.ToStep5);


        // Step5 -> PublicDelivery ; Step5 -> MeteriaMatchLogic3
        builder.onEntry(FSMController.FSMState.Step5).perform(decisionMaker);
        builder.transitions().from(FSMController.FSMState.Step5).toAmong(FSMController.FSMState.MeteriaMatchLogic3, FSMController.FSMState.PublicDelivery)
                .onEach(FSMController.FSMEvent.ToMeteriaMatchLogic3, FSMController.FSMEvent.ToPublicDelivery);

        //Use State Machine
        UntypedStateMachine fsm = builder.newStateMachine(FSMController.FSMState.Open);
        //Open -> Start
        fsm.fire(FSMController.FSMEvent.ToStart, contextData);

        //PublicDelivery -> MeteriaMatchLogicDiagram
        fsm.fire(FSMController.FSMEvent.ToMeteriaMatchLogicDiagram, contextData);

        //MeteriaMatchLogic1 -> Step4   没有素材可投
        fsm.fire(FSMController.FSMEvent.ToStep4, contextData);

        //MeteriaMatchLogic2 -> Step5   没有素材可投
        fsm.fire(FSMController.FSMEvent.ToStep5, contextData);

        System.out.println("Current state is "+fsm.getCurrentState());
        fsm.terminate();
        long end=System.currentTimeMillis();
        System.out.println("方法耗时:"+(end-begin));
    }
}

先初始化contextData和决策类,创建状态机builder,定义transitions流程,通过builder初始化状态Open创建状态机对象fsm。通过fire方法启动状态机执行跳转,如果有条件判断并且一次定义多个transition的需要定义在FSMDecisionMaker 类里。

测试结果

进入Start.
进入Step1.
进入Step2.
进入Step3.
进入Step4.
进入MeteriaMatchLogic2.
Current state is MeteriaMatchLogic2
方法耗时:432

这种方式实现虽然没有Akka FSM 简洁、方便,是用java实现的,而且比Akka轻量级,用注解配合流式Api,可以把更多的注意力放在业务实现上面。缺点是中文资料特别少。

参考资料:
https://github.com/hekailiang/squirrel
http://www.yangguo.info/2015/02/01/squirrel/

4.总结

不管是squirrel-foundation还是Akka FSM,或者是各种状态模式的实现,其实都是归为几个要素:状态state,上下文数据contextDate,事件event,以及动作action。 各种实现都需要初始化数据,初始化状态,按照指定的event进行条件判断,然后触发相应的action。
在项目中应用java的状态模式实际意义不大,在项目代码里面就是用责任链模式实现的(继承了handler类,持有对下一个对象的引用),squirrel-foundation和Akka比会更加轻量级,而且更容易在java项目中使用,但是缺点同样是资料偏少,怕踩坑。其他类似行为树我没找到实现方案,规则引擎学习成本高,而且偏重量级不适合项目业务场景。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容