状态机解决复杂逻辑
开发回顾:
第一代:两个变量控制逻辑
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)