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

狀態機解決複雜邏輯

開發回顧:

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

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)