如何用状态机处理多个状态?
How to handle multiple states with a state machine?
我开始用状态机实现一个系统。但是我到了一个地步,我怀疑状态机是正确的方法。
例如:我有四种状态:
(空闲、通电、断电、工作)
和其他两个州:
(生产、测试)
powerup 和 powerdown 在生产和测试状态下确实表现不同......
如果我有更多的状态,状态的组合就会爆炸......
如何用状态机解决这个问题?
一个状态机可以与另一个状态机共享信号。因此,指示 prod 或 dev 的状态机可以向另一个状态机发送信号。
事实上,如果状态机上只有 2 个状态,则可以为此目的使用一个变量。因此,您将拥有一个状态机,该状态机将根据变量的值执行不同的工作。
这有点难以回答,因为实际用例非常模糊,但这里有一些可能的技巧:
- 为生产+通电、测试+通电、生产+断电、测试+断电创建单独的状态。根据状态组合的复杂性和数量,这可能会很快爆炸。
优点:直接。
缺点:混乱,不能很好地扩展,如果一些状态逻辑在实例之间共享,将会涉及一些复制面食(因此,不太容易维护)
- 使用层次状态机 (HFSM),也就是说,如果您可以在各个状态组之间定义某种层次关系,则特定状态的实现将是一个独立的状态机。
所以在你的例子中,你会有一个 Production/Test 状态机,每个状态机都会在内部实现它自己的 Idle/Powerup/Powerdown/Work 状态机。
如果您仔细考虑一下,这实际上是选项 1 的更简洁的实现。
优点:比选项 1 更具可读性
缺点:假设子状态应该共享一些公共登录,仍然会涉及复制粘贴
- 有 2 个并行状态机,一个处理 Production/Test 状态,一个处理 Idle/Powerup/Powerdown/Work 状态,使用某种黑板或共享内存在机器之间进行通信。
在您的示例中,您的代理或系统将作为上述状态机的容器并依次处理它们。 Production/Test 机器会将一些状态写入共享内存,另一台机器将从中读取并相应地分支其状态逻辑。
优点:可以在不同状态之间共享代码
缺点:可以在不同状态之间共享代码......好吧,说真的,强调共享代码并不总是一个好主意是非常重要的(但这是另一个哲学讨论。只是让确保您正确评估了共享代码的数量与唯一代码或路径的数量,这样您就不会得到一个巨大的 class,它实际上包含 2 个完全独立的代码路径
- 我知道这是理所当然的,但请考虑 FSM 是否是在您的应用程序中表示状态和执行的正确方法。同样,没有足够的上下文来深入研究这一点,这一点本身就是一场哲学辩论——但也要对其他解决方案保持开放的态度。
我会将 'production' 和 'test' 归类为模式,而不是状态。它仍然会有些混乱,但在我看来,区别很重要。
switch(state)
{
case powerup:
switch(mode)
{
case test:
test_powerup_stuff();
break;
case production:
production_powerup_stuff();
break;
default:
break;
}
break;
case powerdown:
switch(mode)
{
case test:
test_powerdown_stuff();
break;
case production:
production_powerdown_stuff();
break;
default:
break;
}
break;
case idle:
do_idle_stuff();
break;
case work:
do_work_stuff();
break;
default:
state = powerdown;
break;
}
难道你需要同一个状态机模型的两个实例?一个用于生产,一个用于测试?
另一种方法是生产和测试可以是一台机器的正交区域。
感觉你的状态机"explodes"在传统的"flat" FSM中很典型(其实就是一般人说的"state-transition explosion"现象)。补救措施是改用 分层状态机 (HSM),它专门抵消了传统 FSM 的 "explosion"。基本上,HSM 允许您将具有相似行为的状态组合在一起(在更高级别的超状态中),从而重用相关状态之间的共同行为。这是一个非常强大的概念,可以带来更加优雅和一致的设计。要了解有关分层状态机的更多信息,您可以阅读文章 "Introduction to Hierarchical State Machines"。
我开始用状态机实现一个系统。但是我到了一个地步,我怀疑状态机是正确的方法。
例如:我有四种状态:
(空闲、通电、断电、工作)
和其他两个州:
(生产、测试)
powerup 和 powerdown 在生产和测试状态下确实表现不同......
如果我有更多的状态,状态的组合就会爆炸......
如何用状态机解决这个问题?
一个状态机可以与另一个状态机共享信号。因此,指示 prod 或 dev 的状态机可以向另一个状态机发送信号。
事实上,如果状态机上只有 2 个状态,则可以为此目的使用一个变量。因此,您将拥有一个状态机,该状态机将根据变量的值执行不同的工作。
这有点难以回答,因为实际用例非常模糊,但这里有一些可能的技巧:
- 为生产+通电、测试+通电、生产+断电、测试+断电创建单独的状态。根据状态组合的复杂性和数量,这可能会很快爆炸。
优点:直接。
缺点:混乱,不能很好地扩展,如果一些状态逻辑在实例之间共享,将会涉及一些复制面食(因此,不太容易维护)
- 使用层次状态机 (HFSM),也就是说,如果您可以在各个状态组之间定义某种层次关系,则特定状态的实现将是一个独立的状态机。
所以在你的例子中,你会有一个 Production/Test 状态机,每个状态机都会在内部实现它自己的 Idle/Powerup/Powerdown/Work 状态机。
如果您仔细考虑一下,这实际上是选项 1 的更简洁的实现。
优点:比选项 1 更具可读性
缺点:假设子状态应该共享一些公共登录,仍然会涉及复制粘贴
- 有 2 个并行状态机,一个处理 Production/Test 状态,一个处理 Idle/Powerup/Powerdown/Work 状态,使用某种黑板或共享内存在机器之间进行通信。
在您的示例中,您的代理或系统将作为上述状态机的容器并依次处理它们。 Production/Test 机器会将一些状态写入共享内存,另一台机器将从中读取并相应地分支其状态逻辑。
优点:可以在不同状态之间共享代码
缺点:可以在不同状态之间共享代码......好吧,说真的,强调共享代码并不总是一个好主意是非常重要的(但这是另一个哲学讨论。只是让确保您正确评估了共享代码的数量与唯一代码或路径的数量,这样您就不会得到一个巨大的 class,它实际上包含 2 个完全独立的代码路径
- 我知道这是理所当然的,但请考虑 FSM 是否是在您的应用程序中表示状态和执行的正确方法。同样,没有足够的上下文来深入研究这一点,这一点本身就是一场哲学辩论——但也要对其他解决方案保持开放的态度。
我会将 'production' 和 'test' 归类为模式,而不是状态。它仍然会有些混乱,但在我看来,区别很重要。
switch(state)
{
case powerup:
switch(mode)
{
case test:
test_powerup_stuff();
break;
case production:
production_powerup_stuff();
break;
default:
break;
}
break;
case powerdown:
switch(mode)
{
case test:
test_powerdown_stuff();
break;
case production:
production_powerdown_stuff();
break;
default:
break;
}
break;
case idle:
do_idle_stuff();
break;
case work:
do_work_stuff();
break;
default:
state = powerdown;
break;
}
难道你需要同一个状态机模型的两个实例?一个用于生产,一个用于测试?
另一种方法是生产和测试可以是一台机器的正交区域。
感觉你的状态机"explodes"在传统的"flat" FSM中很典型(其实就是一般人说的"state-transition explosion"现象)。补救措施是改用 分层状态机 (HSM),它专门抵消了传统 FSM 的 "explosion"。基本上,HSM 允许您将具有相似行为的状态组合在一起(在更高级别的超状态中),从而重用相关状态之间的共同行为。这是一个非常强大的概念,可以带来更加优雅和一致的设计。要了解有关分层状态机的更多信息,您可以阅读文章 "Introduction to Hierarchical State Machines"。