有限狀態(tài)機(jī)FSM的幾種簡(jiǎn)單實(shí)現(xiàn)

『代碼github地址』

標(biāo)簽: 有限狀態(tài)機(jī),Akka fsm,squirrel-foundation,java狀態(tài)模式、責(zé)任鏈模式


1. 有限狀態(tài)機(jī)的概念

有限狀態(tài)機(jī)(英語(yǔ):finite-state machine,縮寫(xiě):FSM)又稱(chēng)有限狀態(tài)自動(dòng)機(jī),簡(jiǎn)稱(chēng)狀態(tài)機(jī),是表示有限個(gè)狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的數(shù)學(xué)模型。

通常FSM包含幾個(gè)要素:狀態(tài)的管理、狀態(tài)的監(jiān)控、狀態(tài)的觸發(fā)、狀態(tài)觸發(fā)后引發(fā)的動(dòng)作。

這些關(guān)系的意思可以這樣理解:

  • State(S) x Event(E) -> Actions (A), State(S’)
  • 如果我們當(dāng)前處于狀態(tài)S,發(fā)生了E事件, 我們應(yīng)執(zhí)行操作A,然后將狀態(tài)轉(zhuǎn)換為S’

下面展示最常見(jiàn)的表示:當(dāng)前狀態(tài)(B)和條件(Y)的組合指示出下一個(gè)狀態(tài)(C)。完整的動(dòng)作信息可以只使用腳注來(lái)增加。包括完整動(dòng)作信息的FSM定義可以使用狀態(tài)表。

條件↓當(dāng)前狀態(tài)→ 狀態(tài)A 狀態(tài)B 狀態(tài)C
條件X
條件Y 狀態(tài)C
條件Z

2. 使用狀態(tài)機(jī)的應(yīng)用背景

在廣告投放項(xiàng)目中由于復(fù)雜的廣告投放邏輯,存在著大量的if-else 判斷類(lèi)似的硬編碼,希望能借助對(duì)fsm模型的調(diào)研,找出理想的實(shí)現(xiàn)方式。

3.有限狀態(tài)機(jī)的幾種實(shí)現(xiàn)

3.1枚舉類(lèi)實(shí)現(xiàn)java狀態(tài)模式

首先在枚舉類(lèi)中 定義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) {}
    },
    ...
   
    //狀態(tài)模式 提取的接口  在常量實(shí)體類(lèi)中實(shí)現(xiàn)抽象方法
    abstract void valid(JavaPlatformMachine pm);
    abstract void first(JavaPlatformMachine pm);
    abstract void businessLine(JavaPlatformMachine pm);
    abstract void district(JavaPlatformMachine pm); 
}      

在enum JavaPlatformState 中,除了狀態(tài)模式 提取的接口外,添加了狀態(tài)機(jī)的各種動(dòng)作action實(shí)現(xiàn)

//狀態(tài)機(jī)的各種動(dòng)作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");}

建立狀態(tài)機(jī)實(shí)體,ContextData是封裝條件的bean類(lèi),初始化狀態(tài)OPEN,在狀態(tài)機(jī)里定義action,調(diào)用對(duì)應(yīng)state的相應(yīng)的方法。

public class ContextData {
    private Boolean isValid_;//廣告位是否有效
    private Boolean isFirst_;//是否第一次請(qǐng)求
    private Boolean isBusinessLine_;//是否屬于業(yè)務(wù)線(xiàn)廣告位
    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);}
}

測(cè)試方法,初始化狀態(tài)機(jī),設(shè)置參數(shù),按次序調(diào)用對(duì)應(yīng)的Action

    JavaPlatformMachine pm = new JavaPlatformMachine();
    pm.data.setValid_(true);// 廣告位是否有效
    pm.data.setFirst_(false);// 是否第一次請(qǐng)求
    pm.data.setBusinessLine_(true);//是否屬于業(yè)務(wù)線(xiàn)廣告位
    pm.data.setDistrict_(true);//是否有地域
    pm.valid();
    pm.first();
    pm.businessLine();
    pm.district();

輸出結(jié)果

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

在設(shè)置參數(shù)下,最后狀態(tài)調(diào)用Top9。
不過(guò)這種方式在枚舉類(lèi)中實(shí)現(xiàn)抽象方法,每個(gè)state下都要覆寫(xiě)(Override)action的抽象方法,顯然不利于拓展。

參考資料:
http://blog.csdn.net/yqj2065/article/details/39371487

3.2抽象類(lèi)實(shí)現(xiàn)java狀態(tài)模式

當(dāng)一個(gè)類(lèi)的某個(gè)成員變量的值變化時(shí),可能導(dǎo)致多個(gè)行為表現(xiàn)得不同。將該成員變量封裝成類(lèi)型的模式,即為狀態(tài)模式(state pattern)。即用多態(tài)來(lái)重構(gòu)分支結(jié)構(gòu)。

首先抽象狀態(tài)類(lèi),定義一個(gè)接口以封裝與Context的一個(gè)特定狀態(tài)相關(guān)的行為

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

Context類(lèi),維護(hù)一個(gè)State子類(lèi)的實(shí)例,這個(gè)實(shí)例定義當(dāng)前的狀態(tài)。

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的初始狀態(tài)
    public StateModeContext(State state , ContextData data )
    {
        this.data = data;
        this.state = state;
    }

    /// 對(duì)請(qǐng)求做處理,并設(shè)置下一個(gè)狀態(tài)
    boolean  trueFlag = true;
    
    public void Request()
    {
        //如果當(dāng)前step 是最后一步  nextStep 不執(zhí)行
        if(state.isFinalflag()){
            trueFlag = false;
        }
        state.Handle(this);
    }
}

最后定義各個(gè)狀態(tài)子類(lèi)

public class State404 extends State {
    @Override
    public void Handle(StateModeContext context) {
        System.out.println("當(dāng)前狀態(tài)是 404  do something");
    }
    @Override
    public boolean isFinalflag() {
        return true;
    }
}

其中設(shè)置一個(gè)FinalFlag 標(biāo)識(shí) 是否是最終狀態(tài)。
在下面state的子類(lèi)里面進(jìn)行條件判斷,并設(shè)置下一個(gè)狀態(tài),這一點(diǎn)有點(diǎn)類(lèi)似責(zé)任鏈模式(在抽象類(lèi)中定義一個(gè)實(shí)例成員變量 next handler 的引用)

public class StateStep1 extends State {
    @Override
    public void Handle(StateModeContext context) {
        System.out.println("當(dāng)前狀態(tài)是 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;
    }
}

測(cè)試類(lèi):設(shè)置初始化數(shù)據(jù)和初始化狀態(tài)stateOpen,根據(jù)狀態(tài)樹(shù)的深度確定循環(huán)迭代次數(shù),進(jìn)行迭代。

public class StateModeTest {
    public static void main(String[] args) {
        // 設(shè)置Context的初始狀態(tài)為ConcreteStateA
        ContextData data = new ContextData(true,false,true,true);
        StateModeContext context = new StateModeContext(new StateOpen(),data);
        // 不斷地進(jìn)行請(qǐng)求,同時(shí)更改狀態(tài)
        int size = 4;// 請(qǐng)求迭代數(shù)
        for(int i = 0 ; i< size+1; i++){
            if(context.trueFlag){
                context.Request();
            }
        }
    }
}

輸出結(jié)果:

當(dāng)前狀態(tài)是 open
open -> step1
當(dāng)前狀態(tài)是 step1
step1 -> step2
當(dāng)前狀態(tài)是 step2
step2 -> step3
當(dāng)前狀態(tài)是 step3
step3 -> Top9(返回9素材)
當(dāng)前狀態(tài)是 Top9(返回9素材)  do something

這種方式實(shí)現(xiàn)狀態(tài)模式,每個(gè)狀態(tài)要new一個(gè)狀態(tài)的子類(lèi),而且手動(dòng)指定循環(huán)迭代次數(shù)通過(guò)迭代方式進(jìn)行事件的調(diào)用。

3.3 Akka FSM 實(shí)現(xiàn)狀態(tài)機(jī)

Akka的狀態(tài)機(jī)是非常簡(jiǎn)潔的實(shí)現(xiàn),充分利用了Scala的許多先進(jìn)的語(yǔ)法糖讓代碼更加簡(jiǎn)潔清晰。是基于A(yíng)kka Actor 實(shí)現(xiàn),封裝了很多自定義的API(實(shí)際上就是DSL)。

在底層,Akka FSM就是一個(gè)繼承了Actor的trait(scala的特征trait就相當(dāng)于java的接口)

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

FSM trait提供了一個(gè)包裝了常規(guī)Actor的DSL,讓我們能集中注意力在更快的構(gòu)建手頭的狀態(tài)機(jī)上。常規(guī)Actor只有一個(gè)receive方法,F(xiàn)SM trait包裝了receive方法的實(shí)現(xiàn)并將調(diào)用指向到一個(gè)特定狀態(tài)機(jī)的處理代碼塊。

class PlatformMachine extends FSM[PlatformState,PlatformData]

上面的 PlatformMachine 是一個(gè)FSM ACTOR。
PlatformState,PlatformData分別為我定義的狀態(tài)和數(shù)據(jù)。在FSM中,有兩個(gè)東西是一直存在的,任何時(shí)間點(diǎn)都有狀態(tài) ,和在狀態(tài)中進(jìn)行共享的數(shù)據(jù)。 這代表所有的fsm的狀態(tài)繼承自PlatformState,而所有在狀態(tài)間共享的數(shù)據(jù)就是PlatformData

在伴生對(duì)象中定義狀態(tài),消息,數(shù)據(jù)。

在Scala的類(lèi)中,與類(lèi)名相同的對(duì)象(object)叫做伴生對(duì)象,類(lèi)和伴生對(duì)象之間可以相互訪(fǎng)問(wèn)私有的方法和屬性。在Scala中沒(méi)有靜態(tài)方法和靜態(tài)字段,但是可以使用object這個(gè)語(yǔ)法結(jié)構(gòu)來(lái)達(dá)到同樣的目的。object是單例模式的,一般也用于存放工具方法和常量,共享單個(gè)不可變的實(shí)例等。

在Scala中樣例類(lèi)(case class)是一中特殊的類(lèi),可用于模式匹配。case class是多例的,后面要跟構(gòu)造參數(shù),case object是單例的。這點(diǎn)其實(shí)和class與object的區(qū)別是一樣的。

object PlatformMachine{
  sealed trait PlatformState
  //定義6個(gè)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 類(lèi)的代碼

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狀態(tài)下的所有Event
    case _ => {
      println("end !")
      stay()
    }
  }

  when(Step1){
    case Event(ToReturnDimension, PlatformData(_,isFirst,_,_))   => {
      //是否第一次請(qǐng)求 /是  返回廣告位大小尺寸數(shù)據(jù)
      if (isFirst equals  true){
        println("goto ReturnDimension!")
        service.returnDimension()
        goto(Off)
      }else{
        println("goto step2")
        goto(Step2)
      }
    }
  }

  when(Step2){
    case Event(ToPreciseAdvertising, PlatformData(_,_,isBusinessLine,_))   => {
      //是否業(yè)務(wù)線(xiàn)廣告位 /是  返回精準(zhǔn)投放
      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狀態(tài)下的所有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   ! ")
  }

}

如上,首先是定義初始化狀態(tài)為open和初始化數(shù)據(jù)。

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

接下來(lái)是狀態(tài)處理器,根據(jù)各個(gè)業(yè)務(wù)來(lái),例如:

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)
      }
    }
  }

我們有一個(gè)初始狀態(tài)(Open),when(open)代碼塊處理Open狀態(tài)的
收到的消息event,ToNotFound由when(ToNotFound)代碼塊來(lái)處理。我提到的消息與常規(guī)我們發(fā)給Actor的消息時(shí)一樣的,消息與數(shù)據(jù)一起包裝過(guò)。包裝后的叫做Event(akka.actor.FSM.Event),看起來(lái)的樣例是這樣case Event(ToNotFound, PlatformData(isValid,_,_,_)),比如我這里獲得了isValid的參數(shù),在Open的狀態(tài)下模式匹配到了ToNotFound,在函數(shù)里面根據(jù)參數(shù)做業(yè)務(wù)判斷,調(diào)用業(yè)務(wù)方法或者通過(guò)調(diào)用goto方法,調(diào)度到下一個(gè)狀態(tài)。

還有一個(gè)event是SetInitData,我這里是設(shè)置自定義初始化數(shù)據(jù)用的,其中有幾個(gè)關(guān)鍵字stay,usingstateData

每一個(gè)被阻塞的case都必須返回一個(gè)State。這個(gè)可以用stay來(lái)完成,含義是已經(jīng)在處理這條消息的最后了,以下是stay方法的源碼實(shí)現(xiàn):

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

其實(shí)也就是調(diào)用了goto。
using方法可以讓我們把改過(guò)的數(shù)據(jù)傳給下個(gè)狀態(tài)。

  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 是如果沒(méi)有匹配到,F(xiàn)SM Actor會(huì)嘗試將我們的消息與whenUnhandled塊中的模式進(jìn)行匹配。
onTransition 是在狀態(tài)變化時(shí)做出反應(yīng)或得到通知。
測(cè)試類(lèi):

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("方法耗時(shí):"+(end-begin));
}

new 一個(gè)狀態(tài)機(jī),對(duì)這個(gè)ActorRef 發(fā)送消息,按順序執(zhí)行。這里的!和scala Actor 編程的模式一樣,表示發(fā)送異步消息,沒(méi)有返回值。
執(zhí)行結(jié)果:

方法耗時(shí):36
SetInitData:true , false ,false ,true  
goto step1
-------------------------onTransition : from Open to Step1  ! 
goto step2
-------------------------onTransition : from Step1 to Step2 ! 
goto PreciseAdvertising!
精準(zhǔn)投放!
-------------------------onTransition : from Step2 to OFF   ! 
end !

由結(jié)果看出來(lái),其也是異步調(diào)用的。
由于網(wǎng)上找的資料都是用繼承Akka的TestKit測(cè)試包來(lái)進(jìn)行測(cè)試的demo,現(xiàn)在我還沒(méi)找到實(shí)際能用于生產(chǎn)上的解決方案。
如我下面代碼:

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("方法耗時(shí):"+(end-begin));
//  system.awaitTermination()
}

其測(cè)試結(jié)果:

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   ! 
方法耗時(shí):638

通過(guò)ActorSystem調(diào)用actorOf的方式 獲得machine這個(gè)ActorRef,進(jìn)行異步發(fā)送消息,我這里是先線(xiàn)程sleep 再關(guān)閉ActorSystem。這塊我用的比較淺,還沒(méi)有找到其他更好的方法。

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

3.4 squirrel state machine 實(shí)現(xiàn)狀態(tài)機(jī)

squirrel-foundation是一款輕量級(jí)的java有限狀態(tài)機(jī)。既支持流式API又支持聲明式創(chuàng)建狀態(tài)機(jī),允許用戶(hù)以一種簡(jiǎn)單方式定義操作方法。這里只介紹狀態(tài)機(jī)squirrel的初級(jí)用法。

3.4.1簡(jiǎn)單操作介紹

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

  • T代表實(shí)現(xiàn)的狀態(tài)機(jī)類(lèi)型。
  • S代表實(shí)現(xiàn)的狀態(tài)類(lèi)型。
  • E代表實(shí)現(xiàn)的事件類(lèi)型。
  • C代表實(shí)現(xiàn)的外部上下文類(lèi)型。

首先得先創(chuàng)建一個(gè)狀態(tài)機(jī)

  • 通過(guò)StateMachineBuilderFactory創(chuàng)建的StateMachineBuilder用來(lái)定義狀態(tài)機(jī)。
  • 所有的狀態(tài)機(jī)實(shí)例會(huì)被同一個(gè)狀態(tài)機(jī)builder創(chuàng)建,該builder共享一份結(jié)構(gòu)化的數(shù)據(jù),從而優(yōu)化內(nèi)存的使用。
  • 狀態(tài)機(jī)builder在生成狀態(tài)機(jī)的時(shí)候使用lazy模式。當(dāng)builder創(chuàng)建第一個(gè)狀態(tài)機(jī)實(shí)例時(shí),包含時(shí)間消耗的狀態(tài)機(jī)定義才會(huì)被創(chuàng)建。但是狀態(tài)機(jī)定義生成之后,接下來(lái)的狀態(tài)機(jī)創(chuàng)建將會(huì)非常快。狀態(tài)機(jī)builder應(yīng)該盡量重用。
UntypedStateMachineBuilder builder = StateMachineBuilderFactory.create(FSMController.class);

狀態(tài)機(jī)builder創(chuàng)建之后,定義狀態(tài)機(jī)的state,transition和action,執(zhí)行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來(lái)定義狀態(tài)機(jī)。

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 下觸發(fā),滿(mǎn)足條件context!=null &&context.getValue()>80)后執(zhí)行該thisMethod方法。

這里的whenMvel使用MVEL來(lái)描述條件,字符:::用來(lái)分離條件名稱(chēng)和條件表達(dá)式。context是預(yù)先定義好的指向當(dāng)前上下文的對(duì)象。

這樣的條件判斷 是在MyState.C 到MyState.D 在GoToD的event 下滿(mǎn)足MyCondition的情況下 才會(huì)CallMethod,但是這里when 只是內(nèi)部條件判斷,只是從from狀態(tài)C to 狀態(tài)D 轉(zhuǎn)移中進(jìn)行條件判斷 判斷不過(guò)則不執(zhí)行狀態(tài)轉(zhuǎn)移。
如果我要取代if-else 條件判斷的需求的話(huà),如果按這種寫(xiě)法 是不是該這么寫(xiě):

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");

這樣子肯定不行,其實(shí)還有API,可以一次定義多個(gè)transition,如下:

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

不過(guò)這樣子,首先在之前就要定義條件才行

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);
            }
        }
}

定義完這個(gè)繼承隱式匿名action的類(lèi),并在里面根據(jù)ContextData寫(xiě)業(yè)務(wù)跳轉(zhuǎn)邏輯。stateMachine.fire(MyEvent.GoToD, context);用戶(hù)可以發(fā)送event以及context,在狀態(tài)機(jī)內(nèi)部觸發(fā)transition。
如下代碼:接下來(lái)在測(cè)試類(lèi)里面實(shí)例化這個(gè)對(duì)象,并且定義在狀態(tài)C入口時(shí)定義這個(gè)action,這樣每次在進(jìn)入狀態(tài)C的時(shí)候就會(huì)執(zhí)行FSMDecisionMaker里面的execute方法了。可以配合該方法參數(shù)列表內(nèi)的 Object to (即transition target state) 進(jìn)行各個(gè)狀態(tài)的條件判斷了。

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

squirrel-foundation還提供注解方式來(lái)定義和擴(kuò)展?fàn)顟B(tài)機(jī)。例子如下:

@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);
    ...
}

注解既可以定義在狀態(tài)機(jī)的實(shí)現(xiàn)類(lèi)上也可以定義在狀態(tài)機(jī)需要實(shí)現(xiàn)的任何接口上面,流式API定義的狀態(tài)機(jī)也可以使用注解(但是接口中定義的方法必須要是public的)。

在接口或者實(shí)現(xiàn)類(lèi)里面注冊(cè)相應(yīng)的method,在注解里面定義entry 或者 exit 該state調(diào)用的method,和對(duì)應(yīng)event 觸發(fā)Transitions 所調(diào)用的method。

3.4.2 根據(jù)業(yè)務(wù)編寫(xiě)Demo

狀態(tài)流程圖如下(ps:簡(jiǎn)書(shū)的markdown 太坑,還沒(méi)有畫(huà)流程圖的功能,文字也不能高亮,下面只有流程圖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/>廣告位是否有定投廣告計(jì)劃}
    d --> |Y|MeteriaMatchLogic1(MeteriaMatchLogic1<br/>物料匹配邏輯)
    d --> |N|Step4(Step4)
    MeteriaMatchLogic1 --> |isNoMaterialDelivery<br/>沒(méi)有素材可投|Step4
    Step4 --> e{isUnFixedPmp<br/>是否有非定投PMP廣告計(jì)劃}
    e --> |Y|MeteriaMatchLogic2(MeteriaMatchLogic2<br/>物料匹配邏輯)
    e --> |N|Step5(Step5)
    MeteriaMatchLogic2 --> |isNoMaterialDelivery<br/>沒(méi)有素材可投|Step5
    Step5 --> f{isUnFixedUnPmp<br/>是否有非定投非PMP廣告計(jì)劃}
    f --> |Y|MeteriaMatchLogic3
    f --> |N|PublicDelivery
  1. 首先定義context 上下文數(shù)據(jù)對(duì)象FSMContextData ,作為StateMachineParameters 里的contextType。
public class FSMContextData {
    private Boolean valid;//廣告位是否有效
    private Boolean privateAD;//是否私有廣告位
    private Boolean privateAdvertiser;//是否私有廣告主
    private Boolean fixedDelivery;//廣告位是否有定投廣告計(jì)劃
    private Boolean unFixedPmp;//是否有非定投PMP廣告計(jì)劃
    private Boolean unFixedUnPmp;//是否有非定投非PMP廣告計(jì)劃
    private Boolean noMaterialDelivery;//是否沒(méi)有素材可投
    ...
    Getter and Construstor with Fields...
    }
  1. 其次定義一個(gè)FSMController類(lèi) ,其繼承AbstractUntypedStateMachine,實(shí)際上就抽象成了一個(gè)狀態(tài)機(jī),里面我注冊(cè)了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("進(jìn)入"+to+".");
    }
    protected void ontoStep1(FSMState from, FSMState to, FSMEvent event, FSMContextData context) {
        System.out.println("進(jìn)入"+to+".");
    }
    protected void ontoStep2(FSMState from, FSMState to, FSMEvent event, FSMContextData context) {
        System.out.println("進(jìn)入"+to+".");
    }
   ...
   
    protected void ontoNotFound(FSMState from, FSMState to, FSMEvent event, FSMContextData context) {
        System.out.println("進(jìn)入"+to+".");
    }
    protected void ontoPublicDelivery(FSMState from, FSMState to, FSMEvent event, FSMContextData context) {
        System.out.println("進(jìn)入"+to+".");
    }
    ...
}

注解@StateMachineParameters用來(lái)聲明狀態(tài)機(jī)泛型參數(shù)類(lèi)型,包括stateType、eventType、contextType。AbstractUntypedStateMachine是任何無(wú)狀態(tài)的狀態(tài)機(jī)的基類(lèi)。 在這個(gè)類(lèi)里面我用枚舉類(lèi)定義了state 和 event,和執(zhí)行的方法即action。
在該類(lèi)上方用注解方式@Transit@State,可以理解為將event 或 state 和 action 做了一次映射。whenMvel條件判斷一樣也可以用在注解里面實(shí)現(xiàn)。

  1. 然后創(chuàng)建一個(gè)決策類(lèi)FSMDecisionMaker,在該類(lèi)里定義業(yè)務(wù)條件,其繼承UntypedAnonymousAction ,并在里面根據(jù)typedTo區(qū)分在哪個(gè)state下,再根據(jù) ContextData寫(xiě)業(yè)務(wù)跳轉(zhuǎn)邏輯,用stateMachine.fire方法進(jìn)行狀態(tài)跳轉(zhuǎn)。
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);//無(wú)效: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()) {//廣告位是否有定投廣告計(jì)劃
                stateMachine.fire(FSMEvent.ToMeteriaMatchLogic1, context);//是:物料匹配邏輯1
            } else {
                stateMachine.fire(FSMEvent.ToStep4, context);//否:step4
            }
        }
        else if(typedTo == FSMState.Step4){
            if(typedContext.isUnFixedPmp()) {//是否有非定投PMP廣告計(jì)劃
                stateMachine.fire(FSMEvent.ToMeteriaMatchLogic2, context);//是:物料匹配邏輯2
            } else {
                stateMachine.fire(FSMEvent.ToStep5, context);//否:step5
            }
        }
        else if(typedTo == FSMState.Step5){
            if(typedContext.isUnFixedUnPmp()) {//是否有非定投非PMP廣告計(jì)劃
                stateMachine.fire(FSMEvent.ToMeteriaMatchLogic3, context);//是:物料匹配邏輯3
            } else {
                stateMachine.fire(FSMEvent.ToPublicDelivery, context);//否:公有投放
            }
        }

    }
}
  1. 最后建立測(cè)試類(lèi)
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   沒(méi)有素材可投
        fsm.fire(FSMController.FSMEvent.ToStep4, contextData);

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

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

先初始化contextData和決策類(lèi),創(chuàng)建狀態(tài)機(jī)builder,定義transitions流程,通過(guò)builder初始化狀態(tài)Open創(chuàng)建狀態(tài)機(jī)對(duì)象fsm。通過(guò)fire方法啟動(dòng)狀態(tài)機(jī)執(zhí)行跳轉(zhuǎn),如果有條件判斷并且一次定義多個(gè)transition的需要定義在FSMDecisionMaker 類(lèi)里。

測(cè)試結(jié)果

進(jìn)入Start.
進(jìn)入Step1.
進(jìn)入Step2.
進(jìn)入Step3.
進(jìn)入Step4.
進(jìn)入MeteriaMatchLogic2.
Current state is MeteriaMatchLogic2
方法耗時(shí):432

這種方式實(shí)現(xiàn)雖然沒(méi)有Akka FSM 簡(jiǎn)潔、方便,是用java實(shí)現(xiàn)的,而且比Akka輕量級(jí),用注解配合流式Api,可以把更多的注意力放在業(yè)務(wù)實(shí)現(xiàn)上面。缺點(diǎn)是中文資料特別少。

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

4.總結(jié)

不管是squirrel-foundation還是Akka FSM,或者是各種狀態(tài)模式的實(shí)現(xiàn),其實(shí)都是歸為幾個(gè)要素:狀態(tài)state,上下文數(shù)據(jù)contextDate,事件event,以及動(dòng)作action。 各種實(shí)現(xiàn)都需要初始化數(shù)據(jù),初始化狀態(tài),按照指定的event進(jìn)行條件判斷,然后觸發(fā)相應(yīng)的action。
在項(xiàng)目中應(yīng)用java的狀態(tài)模式實(shí)際意義不大,在項(xiàng)目代碼里面就是用責(zé)任鏈模式實(shí)現(xiàn)的(繼承了handler類(lèi),持有對(duì)下一個(gè)對(duì)象的引用),squirrel-foundation和Akka比會(huì)更加輕量級(jí),而且更容易在java項(xiàng)目中使用,但是缺點(diǎn)同樣是資料偏少,怕踩坑。其他類(lèi)似行為樹(shù)我沒(méi)找到實(shí)現(xiàn)方案,規(guī)則引擎學(xué)習(xí)成本高,而且偏重量級(jí)不適合項(xiàng)目業(yè)務(wù)場(chǎng)景。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容