狀態機解決複雜邏輯及使用
狀態機解決複雜邏輯
開發回顧:
第一代:兩個變數控制邏輯
1 | 滑鼠 | 切換背景成程式A的視圖/程式B的視圖 | IsBackgroundA 用於表示當前背景的變數 |
---|---|---|---|
切換程式AB激活狀態 | IsAppAActive 用於表示當前激活程式的變數 |
第二代:兩個變數控制邏輯
1 | 滑鼠 | 切換背景成程式A的視圖/程式B的視圖 | IsBackgroundA 用於表示當前背景的變數 |
---|---|---|---|
切換程式AB激活狀態 | IsAppAActive 用於表示當前激活程式的變數 | ||
2 | 快捷鍵 | 切換背景成程式A的視圖/程式B的視圖 | |
切換程式AB激活狀態 |
第三代:三個變數控制邏輯,且出現兩個變數組合來確定關係的情況
1 | 滑鼠 | 切換背景成程式A的視圖/程式B的視圖 | IsBackgroundA 用於表示當前背景的變數 |
---|---|---|---|
切換程式AB激活狀態 | IsAppAActive 用於表示當前激活程式的變數 | ||
2 | 快捷鍵 | 切換背景成程式A的視圖/程式B的視圖 | |
切換程式AB激活狀態 | |||
3 | 程式A激活狀態下 | 滑鼠滑過X區域時,激活B | IsAppAActive =true&&CurrentActiveState 用於記錄滑鼠滑進X區域前的狀態,方便滑出後賦值原始狀態 |
滑鼠滑出X區域時,恢復原始激活狀態 | |||
執行程式B命令時,激活程式B |
第四代:四個變數控制+排列組合
需要在滑鼠和快捷鍵上激活命令上添加動畫,此時我已經覺得程式不可控起來,
第一點,是因為動畫添加的時機不同,激活A時可能需要先激活A在開始動畫,B可能需要先展示動畫再激活B,
第二點,此時需要引入第四個變數來控制動畫效果,因為彈入彈齣動畫是相反的
1 | 滑鼠 | 切換背景成程式A的視圖/程式B的視圖 | IsBackgroundA 用於表示當前背景的變數 |
---|---|---|---|
切換程式AB激活狀態 | IsAppAActive 用於表示當前激活程式的變數 | ||
2 | 快捷鍵 | 切換背景成程式A的視圖/程式B的視圖 | |
切換程式AB激活狀態 | |||
3 | 程式A激活狀態下 | 滑鼠滑過X區域時,激活B | IsAppAActive =true&&CurrentActiveState 用於記錄滑鼠滑進X區域前的狀態,方便滑出後賦值原始狀態 |
滑鼠滑出X區域時,恢復原始激活狀態 | |||
執行程式B命令時,激活程式B | |||
4 | 激活程式時添加動畫 | IsRightAnimation 來選擇動畫展示效果 , IsBackgroundA&&IsAppAActive==》來選擇動畫展示時機 |
第五代:五個變數控制+排列組合
需要多台設備同時開啟程式進行同步,接受來自伺服器的滑鼠鍵盤命令,需要添加一個變數表示是否具有主控權
另外,滑鼠執行方法中添加了多個判斷,包括動畫,有的方法是寫在Anmation.completed方法中,每次執行一個命令所有變數值幾乎都會變一次,遇到問題簡直不能調試
1 | 滑鼠 | 切換背景成程式A的視圖/程式B的視圖 | IsBackgroundA 用於表示當前背景的變數 |
---|---|---|---|
切換程式AB激活狀態 | IsAppAActive 用於表示當前激活程式的變數 | ||
2 | 快捷鍵 | 切換背景成程式A的視圖/程式B的視圖 | |
切換程式AB激活狀態 | |||
3 | 程式A激活狀態下 | 滑鼠滑過X區域時,激活B | IsAppAActive =true&&CurrentActiveState 用於記錄滑鼠滑進X區域前的狀態,方便滑出後賦值原始狀態 |
滑鼠滑出X區域時,恢復原始激活狀態 | |||
執行程式B命令時,激活程式B | |||
4 | 激活程式時添加動畫 | IsRightAnimation 來選擇動畫展示效果 , IsBackgroundA&&IsAppAActive==》來選擇動畫展示時機 | |
5 | 不同設備間同步,添加發送命令和接受命令 | 需要多台設備同時開啟程式進行同步 | IsMaster 是否具有控制權 |
第六代:五個變數控制+排列組合+動畫時機
又加一個動畫時機,寫程式碼沒底,做不下去了感覺,囧
1 | 滑鼠 | 切換背景成程式A的視圖/程式B的視圖 | IsBackgroundA 用於表示當前背景的變數 |
---|---|---|---|
切換程式AB激活狀態 | IsAppAActive 用於表示當前激活程式的變數 | ||
2 | 快捷鍵 | 切換背景成程式A的視圖/程式B的視圖 | |
切換程式AB激活狀態 | |||
3 | 程式A激活狀態下 | 滑鼠滑過X區域時,激活B | IsAppAActive =true&&CurrentActiveState 用於記錄滑鼠滑進X區域前的狀態,方便滑出後賦值原始狀態 |
滑鼠滑出X區域時,恢復原始激活狀態 | |||
執行程式B命令時,激活程式B | |||
4 | 激活程式時添加動畫 | IsRightAnimation 來選擇動畫展示效果 , IsBackgroundA&&IsAppAActive==》來選擇動畫展示時機 | |
5 | 不同設備間同步,添加發送命令和接受命令 | 需要多台設備同時開啟程式進行同步 | IsMaster 是否具有控制權 |
6 | 切換背景時添加動畫 | 切換程式A動畫 | |
切換程式B動畫 |
通過狀態機整理邏輯:
定義的狀態與觸發事件
public enum ModelState
{
MainBackground,
U3DBackground,
U3DActive,
U3DNotActive,
TaskBarNormal
}
public enum ModelEvent
{
SetMainBackground,
SetU3DBackground,
SetU3DActive,
SetU3DNotActive,
IncCavansFunction,
MouseEnterTaskbar,
MouseLeaveTaskbar
}
定義狀態機事件
...
builder.In(ModelState.MainBackground)
.On(ModelEvent.SetMainBackground)
.On(ModelEvent.SetU3DBackground).Goto(ModelState.U3DActive);
builder.In(ModelState.U3DBackground)
.On(ModelEvent.SetMainBackground).Goto(ModelState.MainBackground);
...
為了統一操作流程,我將所有方法定義在狀態進入時觸發
...
builder.In(ModelState.MainBackground)
.ExecuteOnEntry(
() => {
SetTaskbar(false, true);
SetBackground(false);
SetU3DActive(false);
});
builder.In(ModelState.U3DActive)
.ExecuteOnEntry(
() => {
StateMachineMsg += $"------------------------" + Environment.NewLine;
StateMachineMsg += $"ModelState.U3DActive" + Environment.NewLine;
SetTaskbar(true);
SetU3DActive(true);
SetBackground(true);
});
...
之後如果再有什麼調整隻需要給狀態機增加狀態和事件,或者調整狀態就可以了。
狀態及簡單使用:
安裝:
我覺得主要難點是定義狀態和事件,最開始用的時候狀態和事件會分不開。
我們拿上下電梯舉例:
1.0版本:
電梯四個狀態,開門,關門,上一層和下一層
程式碼描述:
builder.In(States.OnFloor)
.On(Events.GoUp).Goto(States.MovingUp)
.On(Events.GoDown).Goto(States.MovingDown)
builder.In(States.MovingUp)
.On(Events.Stop).Goto(States.OnFloor);
builder.In(States.MovingDown)
.On(Events.Stop).Goto(States.OnFloor);
2.0版本
在樓層時,我們增加開門關門的語音提示,我們增加一個關門/開門狀態,在進入狀態時播放提示
builder.In(States.OnFloor)
.On(Events.CloseDoor).Goto(States.DoorClosed)
.On(Events.OpenDoor).Goto(States.DoorOpen)
.On(Events.GoUp).Goto(States.MovingUp)
.On(Events.GoDown).Goto(States.MovingDown)
builder.In(States.MovingUp)
.On(Events.Stop).Goto(States.OnFloor);
builder.In(States.MovingDown)
.On(Events.Stop).Goto(States.OnFloor);
builder.In(States.DoorOpen)
.ExecuteOnEntry(
() => {
Said(「正在開門.」);
});
.On(Events.CloseDoor).Goto(States.DoorClosed)
builder.In(States.DoorClosed)
.ExecuteOnEntry(
() => {
Said(「正在關門.」);
});
.On(Events.OpenDoor).Goto(States.DoorOpen)
3.0版本
我們可以發現,Door狀態只與OnFloor發生關係,Moving狀態也只與OnFloor發生關係
我們可以將定兩個層級關係的狀態來描述這些關係。
builder.In(States.OnFloor)
.On(Events.CloseDoor).Goto(States.DoorClosed)
.On(Events.OpenDoor).Goto(States.DoorOpen)
.On(Events.GoUp)
.On(Events.GoDown)
builder.In(States.Moving)
.On(Events.Stop).Goto(States.OnFloor);
builder.In(States.DoorOpen)
.ExecuteOnEntry(
() => {
Said(「正在開門.」);
});
builder.In(States.DoorClosed)
.ExecuteOnEntry(
() => {
Said(「正在關門.」);
});
//定義層級
builder.DefineHierarchyOn(States.Moving)
.WithHistoryType(HistoryType.Shallow)
.WithInitialSubState(States.MovingUp)
.WithSubState(States.MovingDown);
builder.DefineHierarchyOn(States.OnFloor)
.WithHistoryType(HistoryType.None)
.WithInitialSubState(States.DoorClosed)
.WithSubState(States.DoorOpen);
History Types:
None: 狀態進入初始子狀態。子狀態本身進入其初始子狀態等,直到到達最內層的嵌套狀態。.
Deep: T狀態進入其最後一個活動子狀態。子狀態本身進入其最後的活躍狀態等,直到到達最內層的嵌套狀態.
Shallow: 狀態進入其最後一個活動子狀態。子狀態本身進入其初始子狀態等,直到到達最內層的嵌套狀態.
比如:
當我們去MovingUp的狀態,是先到Moving狀態,再到MovingUp狀態。
4.0版本,添加在樓層檢查超重
builder.In(States.OnFloor)
.On(Events.CloseDoor).Goto(States.DoorClosed)
.On(Events.OpenDoor).Goto(States.DoorOpen)
.On(Events.GoUp)
.If(CheckOverload).Goto(States.MovingUp)
.Otherwise().Execute(this.AnnounceOverload)
.On(Events.GoDown)
.If(CheckOverload).Goto(States.MovingDown)
.Otherwise().Execute(this.AnnounceOverload);
5.0版本 添加電梯異常狀態
builder.DefineHierarchyOn(States.Healthy)
.WithHistoryType(HistoryType.Deep)
.WithInitialSubState(States.OnFloor)
.WithSubState(States.Moving);
builder.DefineHierarchyOn(States.Moving)
.WithHistoryType(HistoryType.Shallow)
.WithInitialSubState(States.MovingUp)
.WithSubState(States.MovingDown);
builder.DefineHierarchyOn(States.OnFloor)
.WithHistoryType(HistoryType.None)
.WithInitialSubState(States.DoorClosed)
.WithSubState(States.DoorOpen);
builder.In(States.Healthy)
.On(Events.Error).Goto(States.Error);
builder.In(States.Error)
.On(Events.Reset).Goto(States.Healthy)
.On(Events.Error);
builder.In(States.OnFloor)
.ExecuteOnEntry(this.AnnounceFloor)
.ExecuteOnExit(Beep)
.ExecuteOnExit(Beep) // just beep a second time
.On(Events.CloseDoor).Goto(States.DoorClosed)
.On(Events.OpenDoor).Goto(States.DoorOpen)
.On(Events.GoUp)
.If(CheckOverload).Goto(States.MovingUp)
.Otherwise().Execute(this.AnnounceOverload)
.On(Events.GoDown)
.If(CheckOverload).Goto(States.MovingDown)
.Otherwise().Execute(this.AnnounceOverload);
builder.In(States.Moving)
.On(Events.Stop).Goto(States.OnFloor);
再也不用變數+IF ELSE了,
添加狀態和事件就可以了
邏輯也更清晰
Demo
tiancai4652/StateMachineSapmle: appccelerate/statemachine wpf Sample (github.com)