带历史状态的层次状态机(HSM with History)
带历史状态的层次状态机(HSM with History)
一、先理解:为什么需要“历史状态”?
先看一个生活场景:
你正在电脑上写代码(编辑状态),突然想查资料,于是打开浏览器(切换到浏览状态)。在浏览器里,你又打开了视频网站看视频(播放状态)。看完后,你关掉浏览器——你希望回到之前写代码的状态,并且光标还在刚才编辑的位置,而不是回到编辑器主菜单。
这就是历史状态的价值:记住你“上次离开时的具体位置”,回来时直接恢复,而不是回到父状态的默认入口。
二、核心概念
2.1 层次状态机(Hierarchical State Machine)
普通状态机(平面状态机)所有状态平级,状态一多,转换表就爆炸。
层次状态机允许状态嵌套:
开机状态(PowerOn) ├── 待机状态(Idle) ← 子状态 └── 工作状态(Working) ← 子状态 ├── 编辑状态(Editing) ← 孙状态 └── 预览状态(Previewing)核心机制:
- 子状态可以继承父状态的行为(没有处理的事件交给父状态)。
- 状态转换可以发生在不同层级,大大简化转换表。
2.2 历史状态(History State)
历史状态是一个伪状态(不真正执行逻辑),它像一个“书签”,记录退出复合状态时所在的子状态。
两种历史类型:
| 类型 | 英文 | 含义 |
|---|---|---|
| 浅历史 | Shallow History | 只记住直接子状态,不继续往下记 |
| 深历史 | Deep History | 记住完整的嵌套路径,回到最深层状态 |
三、工作原理
3.1 层次状态机的转换规则
当事件发生时:
- 当前状态先尝试处理
- 如果当前状态不能处理,向上传递给父状态
- 直到某个状态处理了事件,或到达根状态
3.2 历史状态的工作机制
用图形理解:
┌─────────────────────────────────┐ │ 电视机(TV) │ │ ┌─────────────┐ ┌───────────┐ │ │ │ 关机状态 │ │ 开机状态 │ │ │ │ (Off) │ │ (On) │◀─┼── 浅历史 (H) │ └─────────────┘ │ ┌───────┐ │ │ │ │ │ 直播 │ │ │ │ │ │(Live) │ │ │ │ │ ├───────┤ │ │ │ │ │ 点播 │ │ │ │ │ │(VOD) │ │ │ │ │ └───────┘ │ │ │ └───────────┘ │ └─────────────────────────────────┘浅历史(H):关机再开机,回到上次的直接子状态(比如“直播”或“点播”),但不会回到点播里面的“暂停中”。
深历史(DH):关机再开机,回到上次的完整状态路径(比如“开机 → 点播 → 暂停中”)。
四、完整实例:手机音乐播放器
4.1 状态结构
手机(Phone) ├── 待机(Idle) ← 默认子状态 └── 音乐模式(MusicMode) ← 浅历史 + 深历史 ├── 播放中(Playing) │ ├── 正常播放(NormalPlay) │ └── 随机播放(ShufflePlay) └── 暂停(Paused)4.2 场景演示
| 步骤 | 操作 | 当前状态 | 历史记录 |
|---|---|---|---|
| 1 | 开机 | 待机(Idle) | 无 |
| 2 | 打开音乐App | 音乐模式 → 播放中 → 正常播放 | 无 |
| 3 | 切换到随机播放 | 音乐模式 → 播放中 → 随机播放 | 无 |
| 4 | 暂停 | 音乐模式 → 暂停 | 浅历史:播放中(不记子状态) 深历史:播放中 → 随机播放 |
| 5 | 切换到待机 | 待机 | 历史已记录 |
| 6 | 进入音乐模式 | 如果使用浅历史:回到“播放中”(但不知道是正常还是随机,默认进正常) 如果使用深历史:回到“播放中 → 随机播放” | — |
4.3 伪代码演示(带注释)
// 状态IDtypedefenum{ST_IDLE,// 待机ST_MUSIC_MODE,// 音乐模式(复合状态)ST_PLAYING,// 播放中(音乐模式的子状态)ST_PAUSED,// 暂停(音乐模式的子状态)ST_NORMAL_PLAY,// 正常播放(播放中的子状态)ST_SHUFFLE_PLAY,// 随机播放(播放中的子状态)}State;// 带历史的状态机typedefstruct{State current;State history_shallow;// 浅历史:只记直接子状态State history_deep[4];// 深历史:记录完整路径栈}HsmWithHistory;// 进入音乐模式时的逻辑voidenter_music_mode(HsmWithHistory*hsm,bool use_deep_history){if(use_deep_history&&hsm->history_deep[0]!=ST_NONE){// 深历史:恢复完整路径hsm->current=hsm->history_deep[0];// 例如 ST_SHUFFLE_PLAYrestore_full_stack(hsm->history_deep);}elseif(hsm->history_shallow!=ST_NONE){// 浅历史:只恢复到直接子状态hsm->current=hsm->history_shallow;// 例如 ST_PLAYING// 进入 ST_PLAYING 后,由其默认子状态 ST_NORMAL_PLAY}else{// 无历史:进入默认子状态hsm->current=ST_PLAYING;// 默认}}五、为什么需要“历史状态”?总结三大价值
| 价值 | 说明 |
|---|---|
| 用户体验好 | 从子菜单返回时回到上次位置,而不是主页(手机App、电视机顶盒) |
| 减少状态爆炸 | 不需要为每种“深层位置”单独设计状态 |
| 简化转换逻辑 | 只需要转换到复合状态,历史机制自动处理恢复细节 |
六、一句话记忆
浅历史记直接儿子,深历史记整条血脉;复合状态出去再回来,历史状态让你回到原来的位置。
