Boost C++ 库

世界上最受推崇和设计最精良的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ Coding Standards

UML 简明指南 :: Boost 库文档 - Boost C++ 函数库

UML 简明指南

什么是状态机?

状态机是事物生命周期的描述。它们描述了生命周期的不同阶段、影响它的事件,以及在特定阶段检测到特定事件时事物所做的操作。它们提供了事物动态行为的完整规范。

概念

一开始用状态机的思维方式来思考可能会有点令人惊讶,所以让我们快速浏览一下其中的概念。

状态机、状态、转移、事件

状态机是描述系统行为的具体模型。它由有限数量的状态和转移组成。

image

简单状态没有子状态。它可以有数据、进入和退出行为以及延迟事件。可以为状态(或状态机)提供进入和退出行为(也称为动作),这些行为在进入或离开状态时无论如何都会被执行。状态还可以有内部转移,这些转移不会调用进入或退出行为。状态可以将事件标记为延迟。这意味着如果该状态处于活动状态,则无法处理该事件,但必须保留它。下一次非延迟处理该事件的状态处于活动状态时,将处理该事件,就像刚刚触发它一样。

image

转移是通过事件在活动状态之间切换。动作和保护条件可以附加到转移上。当转移触发时,将执行动作;保护条件是一个布尔运算,首先执行,如果返回 false,则可以阻止转移触发。

image

初始状态标记了状态机的第一个活动状态。它没有实际存在,源自它的转移也没有。

image

子机、正交区域、伪状态

复合状态是包含一个区域或分解为两个或多个区域的状态。复合状态包含自己的状态和区域集。

子机是将另一个状态机插入为状态的状态机。同一个子机可以插入多次。

正交区域是复合状态或子机的部分,每个区域都有自己的一组互斥的状态和转移。

image UML 还定义了许多伪状态,这些伪状态被认为是重要的建模概念,但不足以使其成为一级公民。终止伪状态会终止状态机的执行(MSM 的处理方式略有不同;状态机不会被销毁,但不会再进行任何事件处理)。

image

出口点伪状态会退出复合状态或子机,并强制终止所有包含区域的执行。

image 入口点伪状态允许一种受控的进入复合状态的方式。准确地说,它将复合状态外部的转移连接到复合状态内部的转移。一个重要的点是,这种机制只允许进入一个区域。在上图中,在 region1 中,初始状态将变为活动状态。

image

除了显而易见且更常见的情况(转移终止于子机,如区域案例所示)之外,还有另外两种进入子机的方式。显式入口意味着内部状态是转移的目标。与直接入口不同,不会进行暂定封装,并且只执行一个转移。显式出口是从内部状态到子机外部状态的转移(MSM 不支持)。我不建议使用显式入口或出口。

image

最后一种入口可能性是使用 fork。fork 是显式进入一个或多个区域。其他区域再次使用它们的初始状态激活。

image

历史

UML 定义了两种历史:浅历史和深历史。浅历史是一个伪状态,表示子机最近的子状态。一个子机最多可以有一个浅历史。带有历史伪状态作为目标的转移等同于带有最近子状态作为目标的转移。非常重要的是,只有一个转移可以源自历史。深历史是浅历史,它递归地重新激活最近子状态的子状态。它表示为浅历史,带有星号(圆圈内的 H*)。

image 历史不是一个完全令人满意的概念。首先,只有一个历史伪状态,并且只能有一个转移源自它。所以它们与正交区域混合得不好,因为只能“记住”一个区域。深历史更糟,看起来像临时的补充。历史必须通过转移激活,并且只有一个转移源自它,那么如何建模源自深历史伪状态并指向子机最近子状态的转移呢?作为奖励,它也不灵活,不接受新型历史。让我们面对现实吧,历史在理论上听起来很棒且有用,但 UML 版本并不完全符合要求。因此,MSM 提供了一个对此有用概念的变体。

完成转移 / 匿名转移

完成事件(或转移),也称为匿名转移,定义为没有指定触发它们的事件的转移。这意味着当作为匿名转移源的状态变为活动状态时,如果保护条件允许,此类转移将立即触发。它们在建模算法方面很有用,就像活动图通常做的那样。在实时领域,它们的优点是更容易估算定期执行的操作会持续多久。例如,考虑以下图表。

image

设计者现在可以随时知道他最多需要 4 个转移。通过能够估算转移需要多长时间,他可以估算需要多少时间帧(实时任务通常以固定的时间间隔执行)。如果他还能估算动作的持续时间,他甚至可以使用图算法来更好地估算他的时间要求。

内部转移

内部转移是在活动状态(简单状态或子机)的范围内执行的转移。可以将其视为该状态的自转移,而无需调用进入或退出动作。

转移冲突

如果对于给定的事件,有多个转移被启用,则称它们处于冲突状态。有两种类型的冲突:

  • 对于给定的源状态,定义了多个由同一事件触发的转移。通常,每个转移中的保护条件定义了哪个被触发。

  • 源状态是子机或简单状态,并且冲突发生在状态内部的转移与由同一事件触发且目标是另一个状态的转移之间。

第一种情况很简单;只需在转移表中定义两行或多行,具有相同的源和触发器,并具有不同的保护条件。但是,请注意,UML 标准要求这些条件不重叠。如果重叠,标准什么也没说,只说这是不正确的,因此实现者可以自由地按自己认为合适的方式实现。在 MSM 的情况下,出现在转移表中靠后的转移会先被选中,如果返回 false(表示禁用),则库会尝试前面的转移,依此类推。

image

在第二种情况下,UML 定义了最内部的转移会先被选中,这是有道理的,否则就不可能存在出口点伪状态(内部转移将我们带到出口点,然后包含的状态机可以接管)。

image MSM 会自行处理这两种情况,因此设计者只需要专注于他的状态机和 UML 的细微差别(不重叠的条件),而无需自己实现这种行为。

添加的概念

  • 中断状态:一种终止状态,如果触发了定义的事件,则可以退出。

  • Kleene (任意) 事件:具有 Kleene 事件的转移将接受任何事件作为触发器。与完成转移不同,必须触发事件,并且原始事件在 Kleene 事件中保持可访问。

状态机词汇表

  • 状态机:事物(thing)的生命周期。它由状态、区域、转移和处理传入事件组成。

  • 状态:状态机生命周期中的一个阶段。状态(如子机)可以有进入和退出行为。

  • 事件:引起(或不引起)状态机反应的事件。

  • 转移:状态机对事件做出反应的规范。它指定源状态、触发转移的事件、目标状态(如果转移被触发,它将成为新的活动状态)、保护条件和动作。

  • 动作:在触发转移期间执行的操作。

  • 保护条件:一个布尔运算,能够阻止原本会触发的转移被触发。

  • 转移表:状态机的表示。状态机图是同一模型的图形化但不完整的表示。而转移表则是完整的表示。

  • 初始状态:状态机开始的状态。拥有多个正交区域意味着拥有多个初始状态。

  • 子机:子机是将状态机插入到另一个状态机中作为状态的状态机,并且可以在同一个状态机中找到多次。

  • 正交区域:(逻辑上的)状态机的并行执行流程。状态机的每个区域都有机会处理传入的事件。

  • 终止伪状态:当此状态变为活动状态时,它会终止整个状态机的执行。然而,MSM 不会销毁状态机,这与 UML 标准的要求不同,允许您保留状态机的所有数据。

  • 进入/退出伪状态:为子机定义,并定义为子机外部的转移和子机内部的转移之间的连接。这是一种通过预定点进出子机的方式。

  • Fork:Fork 允许显式进入子机的多个正交区域。

  • 历史:历史是记住子机活动状态的一种方式,以便子机下次变为活动状态时可以在其最后活动状态下继续。

  • 完成事件(也称为完成/匿名转移):当转移没有命名事件触发时,一旦源状态处于活动状态,它就会自动触发,除非保护条件禁止。

  • 转移冲突:如果对于给定的源状态和传入事件,有多个可能的转移,则存在冲突。UML 指定保护条件必须解决冲突。

  • 内部转移:从状态到自身,但没有调用退出和进入动作的转移。