在大屏或电脑上体验更佳——动画和操作区域更宽敞。
输入

状态机

状态机记住当前状态,读取一个事件,再用转移规则决定下一个状态和动作。查找依据是 (state, event)。单看事件不足以决定行为,同一个 push 可以表示拒绝,也可以表示放行。

以闸机为例:规则并非“推一下就做某事”,而是“锁定时推,拒绝”“解锁后推,放行”。事件和记住的状态共同决定结果。

状态机如何工作

本课的确定性有限状态机有四部分:

  • 有限的状态集合:locked、unlocked。
  • 有限的输入事件集合:coin、push。
  • 以 (state, event) 为键的转移表。
  • 初始状态:locked。

每次读取事件,把它与当前状态组成键,在表中查找。匹配行给出 next_state 和要输出的 action。随后执行 state = next_state,再处理下一个事件。

图中边的标签按 event / action 排列。例如 coin / unlock 表示事件是 coin,输出动作是 unlock。图形展示关系,表格精确定义执行行为;两者不一致时,应以转移表为准。

自环也是转移。locked + push 保持锁定并输出 reject;unlocked + coin 保持解锁并输出 thank。状态没变,事件仍已处理,也产生了动作。

逐步演示

默认事件序列是 push, coin, push, coin, coin, push,初始状态为 locked。

  1. 开始。 表中有四条合法规则,覆盖两个状态下的两个事件。
  2. 读取 push。 (locked, push) 指定下一个状态 locked、动作 reject。闸机拒绝推动,沿自环保持锁定。
  3. 读取 coin。 (locked, coin) 指定 unlocked 和 unlock,闸机解锁。
  4. 再次读取 push。 事件与第 2 步相同,键却变成 (unlocked, push),因此输出 pass,回到 locked。
  5. 读取 coin。 (locked, coin) 再次解锁。
  6. 再次读取 coin。 (unlocked, coin) 保持 unlocked,输出 thank。多投一枚硬币不会上锁,而是走另一个自环。
  7. 读取最后的 push。 (unlocked, push) 让人通过,再回到 locked。

完整记录中,push 和 coin 各有两种含义,取决于发生时的状态。决定行为的是 (state, event)。

再切换到 missing-rule。先读取合法的 coin,从 locked 进入 unlocked;随后读取 timeout。表中没有 (unlocked, timeout),执行就在这里停止,不会自行补出规则。

当前状态为什么重要

若只按事件查表,push 的含义就不明确:锁定时应 reject,解锁时应 pass。两者在各自状态下都正确,丢掉状态就无法选择。

许多系统都依靠这种记忆。TCP 端点在 LISTEN、SYN-SENT、ESTABLISHED 中处理报文的方式不同;解析器在数字内、字符串内、分隔符后,对同一字符的解释不同;界面按钮在 idle、loading、disabled 时也可能对点击作出不同响应。

大型系统会增加状态、事件、守卫条件和附带数据,但仍要结合当前状态与输入决定下一步。

边界情况

  • 没有事件。 保持初始状态 locked,不查询任何规则。
  • 同一状态下重复事件。 锁定时的 push, push 两次输出 reject,两次都保持锁定。
  • 不同状态下重复事件。 投币前后的 push 不同,因为状态改变了查找键。
  • 未知事件。 表中没有 timeout 的规则,轨迹停在缺失转移处。
  • 自环。 下一个状态等于当前状态,也仍然消耗事件并输出动作。

常见错误

  • 只把事件当成输入。 转移函数使用 (state, event),不只是 event。
  • 把自环当成无操作。 自环也可以拒绝、致谢、记录日志、重试或触发警报。
  • 只画图,不定义表。 图便于阅读,表让行为精确。
  • 未说明默认规则。 如果缺失行都默认“原地不动”,这也是规则,应明确写出。
  • 忘记初始状态。 同一事件序列从不同状态开始,可能得到不同轨迹。

本课的简化

本课一次只有一个当前状态和一个事件,每个合法转移对应一行规则。实际系统可能增加计时器、并发参与者、守卫条件、重试和转移携带的数据。这些机制仍以一个前提为基础:先知道系统处于什么状态,才能解释事件。