狀態機解決複雜邏輯及使用

猝不及防發表於2021-05-14

狀態機解決複雜邏輯

開發回顧:

第一代:兩個變數控制邏輯

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)

相關文章