狀態機解決複雜邏輯
開發回顧:
第一代:兩個變數控制邏輯
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)