標(biāo)簽: 有限狀態(tài)機,Akka fsm,squirrel-foundation帘营,java狀態(tài)模式、責(zé)任鏈模式
1. 有限狀態(tài)機的概念
有限狀態(tài)機(英語:finite-state machine,縮寫:FSM)又稱有限狀態(tài)自動機补鼻,簡稱狀態(tài)機胀葱,是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動作等行為的數(shù)學(xué)模型。
通常FSM包含幾個要素:狀態(tài)的管理唇牧、狀態(tài)的監(jiān)控罕扎、狀態(tài)的觸發(fā)、狀態(tài)觸發(fā)后引發(fā)的動作丐重。
這些關(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’
下面展示最常見的表示:當(dāng)前狀態(tài)(B)和條件(Y)的組合指示出下一個狀態(tài)(C)扮惦。完整的動作信息可以只使用腳注來增加臀蛛。包括完整動作信息的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)機的應(yīng)用背景
在廣告投放項目中由于復(fù)雜的廣告投放邏輯崖蜜,存在著大量的if-else 判斷類似的硬編碼浊仆,希望能借助對fsm模型的調(diào)研,找出理想的實現(xiàn)方式豫领。
3.有限狀態(tài)機的幾種實現(xiàn)
3.1枚舉類實現(xiàn)java狀態(tà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)模式 提取的接口 在常量實體類中實現(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)機的各種動作action實現(xiàn)
//狀態(tài)機的各種動作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)機實體,ContextData是封裝條件的bean類洲劣,初始化狀態(tài)OPEN,在狀態(tài)機里定義action,調(diào)用對應(yīng)state的相應(yīng)的方法。
public class ContextData {
private Boolean isValid_;//廣告位是否有效
private Boolean isFirst_;//是否第一次請求
private Boolean isBusinessLine_;//是否屬于業(yè)務(wù)線廣告位
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);}
}
測試方法课蔬,初始化狀態(tài)機囱稽,設(shè)置參數(shù),按次序調(diào)用對應(yīng)的Action
JavaPlatformMachine pm = new JavaPlatformMachine();
pm.data.setValid_(true);// 廣告位是否有效
pm.data.setFirst_(false);// 是否第一次請求
pm.data.setBusinessLine_(true);//是否屬于業(yè)務(wù)線廣告位
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战惊。
不過這種方式在枚舉類中實現(xiàn)抽象方法,每個state下都要覆寫(Override)action的抽象方法同欠,顯然不利于拓展样傍。
參考資料:
http://blog.csdn.net/yqj2065/article/details/39371487
3.2抽象類實現(xiàn)java狀態(tài)模式
當(dāng)一個類的某個成員變量的值變化時横缔,可能導(dǎo)致多個行為表現(xiàn)得不同。將該成員變量封裝成類型的模式衫哥,即為狀態(tài)模式(state pattern)茎刚。即用多態(tài)來重構(gòu)分支結(jié)構(gòu)。
首先抽象狀態(tài)類撤逢,定義一個接口以封裝與Context的一個特定狀態(tài)相關(guān)的行為
public abstract class State {
public abstract void Handle(StateModeContext context );
public abstract boolean isFinalflag();
}
Context類膛锭,維護(hù)一個State子類的實例,這個實例定義當(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;
}
/// 對請求做處理初狰,并設(shè)置下一個狀態(tài)
boolean trueFlag = true;
public void Request()
{
//如果當(dāng)前step 是最后一步 nextStep 不執(zhí)行
if(state.isFinalflag()){
trueFlag = false;
}
state.Handle(this);
}
}
最后定義各個狀態(tà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è)置一個FinalFlag 標(biāo)識 是否是最終狀態(tài)。
在下面state的子類里面進(jìn)行條件判斷互例,并設(shè)置下一個狀態(tài)奢入,這一點有點類似責(zé)任鏈模式(在抽象類中定義一個實例成員變量 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;
}
}
測試類:設(shè)置初始化數(shù)據(jù)和初始化狀態(tài)stateOpen,根據(jù)狀態(tài)樹的深度確定循環(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)行請求腥光,同時更改狀態(tài)
int size = 4;// 請求迭代數(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
這種方式實現(xiàn)狀態(tài)模式,每個狀態(tài)要new一個狀態(tài)的子類糊秆,而且手動指定循環(huán)迭代次數(shù)通過迭代方式進(jìn)行事件的調(diào)用武福。
3.3 Akka FSM 實現(xiàn)狀態(tài)機
Akka的狀態(tài)機是非常簡潔的實現(xiàn),充分利用了Scala的許多先進(jìn)的語法糖讓代碼更加簡潔清晰痘番。是基于Akka Actor 實現(xiàn)捉片,封裝了很多自定義的API(實際上就是DSL)。
在底層汞舱,Akka FSM就是一個繼承了Actor的trait(scala的特征trait就相當(dāng)于java的接口)
trait FSM[S, D] extends Actor with Listeners with ActorLogging { ...}
FSM trait提供了一個包裝了常規(guī)Actor的DSL伍纫,讓我們能集中注意力在更快的構(gòu)建手頭的狀態(tài)機上。常規(guī)Actor只有一個receive方法兵拢,F(xiàn)SM trait包裝了receive方法的實現(xiàn)并將調(diào)用指向到一個特定狀態(tài)機的處理代碼塊翻斟。
class PlatformMachine extends FSM[PlatformState,PlatformData]
上面的 PlatformMachine 是一個FSM ACTOR。
PlatformState,PlatformData分別為我定義的狀態(tài)和數(shù)據(jù)说铃。在FSM中,有兩個東西是一直存在的嘹履,任何時間點都有狀態(tài) 腻扇,和在狀態(tài)中進(jìn)行共享的數(shù)據(jù)。 這代表所有的fsm的狀態(tài)繼承自PlatformState砾嫉,而所有在狀態(tài)間共享的數(shù)據(jù)就是PlatformData
在伴生對象中定義狀態(tài)幼苛,消息,數(shù)據(jù)焕刮。
在Scala的類中舶沿,與類名相同的對象(object)叫做伴生對象墙杯,類和伴生對象之間可以相互訪問私有的方法和屬性。在Scala中沒有靜態(tài)方法和靜態(tài)字段括荡,但是可以使用object這個語法結(jié)構(gòu)來達(dá)到同樣的目的高镐。object是單例模式的,一般也用于存放工具方法和常量,共享單個不可變的實例等畸冲。
在Scala中樣例類(case class)是一中特殊的類嫉髓,可用于模式匹配。case class是多例的邑闲,后面要跟構(gòu)造參數(shù)算行,case object是單例的。這點其實和class與object的區(qū)別是一樣的苫耸。
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狀態(tài)下的所有Event
case _ => {
println("end !")
stay()
}
}
when(Step1){
case Event(ToReturnDimension, PlatformData(_,isFirst,_,_)) => {
//是否第一次請求 /是 返回廣告位大小尺寸數(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ù)線廣告位 /是 返回精準(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))
接下來是狀態(tài)處理器褪子,根據(jù)各個業(yè)務(wù)來偷霉,例如:
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)
}
}
}
我們有一個初始狀態(tài)(Open),when(open)代碼塊處理Open狀態(tài)的
收到的消息event褐筛,ToNotFound由when(ToNotFound)代碼塊來處理类少。我提到的消息與常規(guī)我們發(fā)給Actor的消息時一樣的,消息與數(shù)據(jù)一起包裝過渔扎。包裝后的叫做Event(akka.actor.FSM.Event)
硫狞,看起來的樣例是這樣case Event(ToNotFound, PlatformData(isValid,_,_,_))
,比如我這里獲得了isValid的參數(shù),在Open的狀態(tài)下模式匹配到了ToNotFound,在函數(shù)里面根據(jù)參數(shù)做業(yè)務(wù)判斷晃痴,調(diào)用業(yè)務(wù)方法或者通過調(diào)用goto
方法残吩,調(diào)度到下一個狀態(tài)。
還有一個event是SetInitData,我這里是設(shè)置自定義初始化數(shù)據(jù)用的倘核,其中有幾個關(guān)鍵字stay
,using
和stateData
泣侮。
每一個被阻塞的case都必須返回一個State。這個可以用stay
來完成紧唱,含義是已經(jīng)在處理這條消息的最后了,以下是stay
方法的源碼實現(xiàn):
final def stay(): State = goto(currentState.stateName) // cannot directly use currentState because of the timeout field
其實也就是調(diào)用了goto
活尊。
using
方法可以讓我們把改過的數(shù)據(jù)傳給下個狀態(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
是如果沒有匹配到漏益,F(xiàn)SM Actor會嘗試將我們的消息與whenUnhandled塊中的模式進(jìn)行匹配蛹锰。
onTransition
是在狀態(tài)變化時做出反應(yīng)或得到通知。
測試類:
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 一個狀態(tài)機绰疤,對這個ActorRef 發(fā)送消息铜犬,按順序執(zhí)行。這里的!
和scala Actor 編程的模式一樣癣猾,表示發(fā)送異步消息敛劝,沒有返回值。
執(zhí)行結(jié)果:
方法耗時: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é)果看出來纷宇,其也是異步調(diào)用的夸盟。
由于網(wǎng)上找的資料都是用繼承Akka的TestKit
測試包來進(jìn)行測試的demo,現(xiàn)在我還沒找到實際能用于生產(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("方法耗時:"+(end-begin));
// system.awaitTermination()
}
其測試結(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 !
方法耗時:638
通過ActorSystem調(diào)用actorOf的方式 獲得machine這個ActorRef满俗,進(jìn)行異步發(fā)送消息,我這里是先線程sleep 再關(guān)閉ActorSystem作岖。這塊我用的比較淺唆垃,還沒有找到其他更好的方法。
參考資料:
http://udn.yyuap.com/doc/akka-doc-cn/2.3.6/scala/book/chapter3/07_fsm.html
http://www.reibang.com/p/41905206b3b3
http://www.cnphp6.com/archives/29029
3.4 squirrel state machine 實現(xiàn)狀態(tài)機
squirrel-foundation是一款輕量級的java有限狀態(tài)機痘儡。既支持流式API又支持聲明式創(chuàng)建狀態(tài)機辕万,允許用戶以一種簡單方式定義操作方法。這里只介紹狀態(tài)機squirrel的初級用法沉删。
3.4.1簡單操作介紹
state machine(T), state(S), event(E) and context(C)
- T代表實現(xiàn)的狀態(tài)機類型渐尿。
- S代表實現(xiàn)的狀態(tài)類型。
- E代表實現(xiàn)的事件類型矾瑰。
- C代表實現(xiàn)的外部上下文類型砖茸。
首先得先創(chuàng)建一個狀態(tài)機
- 通過StateMachineBuilderFactory創(chuàng)建的StateMachineBuilder用來定義狀態(tài)機。
- 所有的狀態(tài)機實例會被同一個狀態(tài)機builder創(chuàng)建殴穴,該builder共享一份結(jié)構(gòu)化的數(shù)據(jù)凉夯,從而優(yōu)化內(nèi)存的使用。
- 狀態(tài)機builder在生成狀態(tài)機的時候使用lazy模式采幌。當(dāng)builder創(chuàng)建第一個狀態(tài)機實例時劲够,包含時間消耗的狀態(tài)機定義才會被創(chuàng)建。但是狀態(tài)機定義生成之后休傍,接下來的狀態(tài)機創(chuàng)建將會非痴饕铮快。狀態(tài)機builder應(yīng)該盡量重用磨取。
UntypedStateMachineBuilder builder = StateMachineBuilderFactory.create(FSMController.class);
狀態(tài)機builder創(chuàng)建之后人柿,定義狀態(tài)機的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來定義狀態(tài)機寝衫。
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ā)顷扩,滿足條件context!=null &&context.getValue()>80)
后執(zhí)行該thisMethod
方法。
這里的whenMvel
使用MVEL來描述條件慰毅,字符:::
用來分離條件名稱和條件表達(dá)式。context
是預(yù)先定義好的指向當(dāng)前上下文的對象扎阶。
這樣的條件判斷 是在MyState.C 到MyState.D 在GoToD的event 下滿足MyCondition的情況下 才會CallMethod汹胃,但是這里when 只是內(nèi)部條件判斷婶芭,只是從from狀態(tài)C to 狀態(tài)D 轉(zhuǎn)移中進(jìn)行條件判斷 判斷不過則不執(zhí)行狀態(tài)轉(zhuǎn)移。
如果我要取代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的類,并在里面根據(jù)ContextData寫業(yè)務(wù)跳轉(zhuǎn)邏輯轨奄。stateMachine.fire(MyEvent.GoToD, context);
用戶可以發(fā)送event以及context孟害,在狀態(tài)機內(nèi)部觸發(fā)transition。
如下代碼:接下來在測試類里面實例化這個對象挪拟,并且定義在狀態(tài)C入口時定義這個action挨务,這樣每次在進(jìn)入狀態(tài)C的時候就會執(zhí)行FSMDecisionMaker里面的execute方法了∮褡椋可以配合該方法參數(shù)列表內(nèi)的 Object to (即transition target state) 進(jìn)行各個狀態(tài)的條件判斷了谎柄。
FSMDecisionMaker decisionMaker = new FSMDecisionMaker("DecisionMaker");
builder.onEntry(MyState.C).perform(decisionMaker);
squirrel-foundation
還提供注解方式來定義和擴展?fàn)顟B(tài)機。例子如下:
@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)機的實現(xiàn)類上也可以定義在狀態(tài)機需要實現(xiàn)的任何接口上面,流式API定義的狀態(tài)機也可以使用注解(但是接口中定義的方法必須要是public的)惯雳。
在接口或者實現(xiàn)類里面注冊相應(yīng)的method朝巫,在注解里面定義entry 或者 exit 該state調(diào)用的method,和對應(yīng)event 觸發(fā)Transitions 所調(diào)用的method。
3.4.2 根據(jù)業(yè)務(wù)編寫Demo
狀態(tài)流程圖如下(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
- 首先定義context 上下文數(shù)據(jù)對象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...
}
- 其次定義一個FSMController類 恩商,其繼承AbstractUntypedStateMachine,實際上就抽象成了一個狀態(tài)機变逃,里面我注冊了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
用來聲明狀態(tài)機泛型參數(shù)類型,包括stateType怠堪、eventType揽乱、contextType。AbstractUntypedStateMachine
是任何無狀態(tài)的狀態(tài)機的基類粟矿。 在這個類里面我用枚舉類定義了state 和 event凰棉,和執(zhí)行的方法即action。
在該類上方用注解方式@Transit
和@State
陌粹,可以理解為將event 或 state 和 action 做了一次映射撒犀。whenMvel
條件判斷一樣也可以用在注解里面實現(xiàn)。
- 然后創(chuàng)建一個決策類FSMDecisionMaker,在該類里定義業(yè)務(wù)條件或舞,其繼承UntypedAnonymousAction 荆姆,并在里面根據(jù)typedTo區(qū)分在哪個state下,再根據(jù) ContextData寫業(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);//無效: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);//否:公有投放
}
}
}
}
- 最后建立測試類
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和決策類,創(chuàng)建狀態(tài)機builder诈豌,定義transitions流程仆救,通過builder初始化狀態(tài)Open創(chuàng)建狀態(tài)機對象fsm。通過fire方法啟動狀態(tài)機執(zhí)行跳轉(zhuǎn)矫渔,如果有條件判斷并且一次定義多個transition的需要定義在FSMDecisionMaker 類里彤蔽。
測試結(jié)果
進(jìn)入Start.
進(jìn)入Step1.
進(jìn)入Step2.
進(jìn)入Step3.
進(jìn)入Step4.
進(jìn)入MeteriaMatchLogic2.
Current state is MeteriaMatchLogic2
方法耗時:432
這種方式實現(xiàn)雖然沒有Akka FSM 簡潔、方便蚌斩,是用java實現(xiàn)的铆惑,而且比Akka輕量級,用注解配合流式Api,可以把更多的注意力放在業(yè)務(wù)實現(xiàn)上面送膳。缺點是中文資料特別少员魏。
參考資料:
https://github.com/hekailiang/squirrel
http://www.yangguo.info/2015/02/01/squirrel/
4.總結(jié)
不管是squirrel-foundation還是Akka FSM,或者是各種狀態(tài)模式的實現(xiàn)叠聋,其實都是歸為幾個要素:狀態(tài)state撕阎,上下文數(shù)據(jù)contextDate,事件event,以及動作action碌补。 各種實現(xiàn)都需要初始化數(shù)據(jù)虏束,初始化狀態(tài),按照指定的event進(jìn)行條件判斷厦章,然后觸發(fā)相應(yīng)的action镇匀。
在項目中應(yīng)用java的狀態(tài)模式實際意義不大,在項目代碼里面就是用責(zé)任鏈模式實現(xiàn)的(繼承了handler類袜啃,持有對下一個對象的引用)汗侵,squirrel-foundation和Akka比會更加輕量級,而且更容易在java項目中使用群发,但是缺點同樣是資料偏少晰韵,怕踩坑。其他類似行為樹我沒找到實現(xiàn)方案熟妓,規(guī)則引擎學(xué)習(xí)成本高雪猪,而且偏重量級不適合項目業(yè)務(wù)場景。