状态机解决复杂逻辑及使用

状态机解决复杂逻辑

开发回顾:

第一代:两个变量控制逻辑

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)

上一篇:Highcharts 曲线上的借点不显示


下一篇:vue3 computed