SysML 活动图(Activity Diagram)
活动图是能够用来传达系统动态行为信息的三种SysML图之一。活动图可以表示各种各样的活动,甚至可以描述复杂的行为。对象节点可以通过活动对事件、能力和数据的流建模,使用控制节点可以掌控活动的执行。活动分区可以为系统结构分配系统行为。
1、目的
活动图是一种行为图,它是系统的一种动态视图,说明随着时间的推移行为和事件的发生序列。这与结构图(BDD、IBD和参数图)相对,结构图都是静态视图,不会表达任何动态的时间,或者系统及其环境的变化。
活动图、序列图和状态机图是SysML提供的指定系统行为的三种选择。所有三种图都可以表达连续和并发的行为,以及随着时间的推移发生的事件。然而,每种图都有其优点和缺点,你需要根据看图者的需求进行选择。
活动图特别擅长通过行为表示对象——事件、能量或者数据——的流动,关注系统操作时,对象是如何在行为的执行过程中被访问和修改的。它的关键优势在于可读性,活动图可以表达复杂的控制逻辑,这要比序列图和状态机图更强。另外,活动图是唯一能够说明连续系统行为的图。
活动图确实也存在缺点,它略显模糊。活动图说明执行动作的顺序,它们可以有选择地说明哪种结构执行了哪个动作。然而它们并没有提供任何机制来说明哪个结构触发哪个动作(相对而言,序列图可以说明所有三种信息)。
由于上述原因,当与利益相关者协作定义问题空间,并指定系统所需要的行为时,建模者会使用活动图作为分析工具。活动图对于详细设计——即适用于系统实现的、明确的行为说明——并不是一种特别有用的工具。
2、何时创建活动图
因为活动图作为一种分析工具最为有效,所以在你需要和利益相关者沟通,想要捕获系统及其执行者期望的行为时,它是首先要创建的行为图。你还会使用活动图与团队中的其他成员沟通,以捕获系统内部组成部分期望的行为。简而言之,创建活动图并不会与系统生命周期的特定阶段绑定。
某些建模方法会要求你创建活动图作为图形化用例说明书。如果这样实践,那么你就会在为系统模型添加新的用例时创建活动图。
3、活动图外框
活动图的类型缩写是act。其中唯一允许使用的模型元素类型是活动。活动图的外框表示你在系统模型某处定义的单个活动。
活动本身是一种模型元素。它是一种行为。它也是一种命名空间,和模块以及包类似。因此它可以包含一系列系统层级关系中已经命名的元素——节点和边缘。你可以在相关联的活动图的外框内显示那些包含的元素。
你要知道,活动和活动图并不一样。当使用活动这个术语的时候,指的是一 个模型元素,而不是相关的图。模型的图永远都不是模型本身,它只是模型的一种视图。在你的系统中定义一个活动,而不把它显示在活动图中,是完全没有问题的。但大多数情况下,你都会显示它。
图6.1中的图头部告诉我们,这个活动图的外框代表名为执行Hohmann转换的活动,它存在于模型层级关系中的某处。这个图的名称是“用例说明书”。这个名称传达了图的目的:作为同样名称的用例的图形化说明。
4、令牌流
贯穿本文,我都会使用令牌这个术语,因为活动是建立在令牌流的概念之上的。 并且,针对活动中各种类型的节点和边缘的规则也是用令牌流这个术语说明的。
令牌流是一种抽象的概念。令牌不是模型元素,你不会在系统模型中创建它们,你也不会在活动图中显示它们。令牌的想法和SysML关于活动、节点和边缘的定义紧密相关。你必须依赖于你的想象来理解活动中的令牌流,而那个活动会给予各种类型节点和边缘的规则。
我发现一种有助于理解令牌概念的方法,就是把令牌当作垄断游戏卡,它会在活动图之间移动,跨越边缘,从一个节点到另一个节点。可以有多个令牌流动通过活动的一次执行。每个令牌都基于其本身的类型和状态,定义活动的动作和控制逻辑以及在活动执行过程中发生的事件,不依赖其他令牌进行移动。
令牌代表的是什么呢?答案包括两个部分。首先,有两种类型的令牌:对象令牌和控制令牌。我从简单的开始介绍。
对象令牌代表的是在活动中流动的事件、能量或者数据的实例。它可以从总体上代表一个活动的输入或者输出,并且可以代表活动中一个动作的输入或输出。正式的说法是,一个对象令牌代表你在模型层级关系中某处创建的模块、值类型或者信号的实例(以定义事件、能量或者数据的类型)。正如你可能期望的,会有多个对象令牌流动通过活动的单次执行。
控制令牌……什么都不代表。我是说不代表任何物理的东西。它没有类型(模块、值类型或者信号)。控制令牌只表示活动的哪个动作在活动执行的特定时刻处于启用状态。可能会有多个控制令牌流过活动的单次执行。那样意味着活动中的多个动作同时处于启用状态。
我知道现在这还非常抽象,但在开始的时候有了这些基础,你会更好地理解稍后的例子。在我讨论能够出现在活动图中各种元素的时候,就会根据令牌流说明那个元素的规则。规则有很多,开始看起来会让人不知所措。但随着你不断阅读和创建活动图,就会慢慢从整体上理解规则的意义,你终可以解释和说明复杂的系统行为。
5、基本动作
动作是一种可以存在于活动之中的节点,它是为活动基本的功能单元建模的节点。一个动作代表某种类型的处理或者转换,它会在系统操作过程中活动被执行的时候发生。
基本动作的标识法是圆角矩形。我说“基本”,是因为有多种特殊类型的动作, 每个都有其自身的标识法。
你可以在活动中输入任何想要的行为描述。你的描述会显示为活动图上圆角矩形中的字符串。常见的是,系统建模者会把动作写成用自然语言(像英语)表述的动词短语。图6.2显示了一个示例。
尽管SysML不要求,但我还是建议,最好把动作写成一个短语,由有力、无歧义的动词开始。另外,你应该避免把多个动词放在一个动作中,而应该把它分解成多个动作。例如,不要写“验证并保存命令”,而应该创建两个连续的动作:“验证命令”和“保存命令”。
如果不使用自然语言,你还可以使用正式的编程语言(像C、Java、Verilog或者Modelica)来描述活动图中的动作。SysML把这样的声明叫做不透明表达式。这个称呼并不重要,但是你应该知道不透明表达式有两个部分:语言和主体。你会使用花括号来指定语言,它位于主体之前。图6.3显示了带有不透明表达式的动作的例子,它由c语言编写。(但注意,不透明表达式并不是只能使用编程语言。)
系统建模者很少会把动作写成不透明表达式。开发团队会经常在活动图中这么做,创建那些图用于沟通他们的设计。但是,那些开发团队的利益相关者需要知道如何解释包含不透明表达式的活动图。
一个有用且有意义的活动总是会包含一个以上动作(如图6.1所示)。你会使用边缘来连接活动中的动作,边缘会定义排好序的(有时是并发的)序列。那些序列会从整体上描述活动。和说明文字描述一样,好的活动图表述清晰。
你使用活动图表述的不仅仅是动作的序列,它还可以表达对象流——总体上来说就是那些动作和活动的输人和输出。(你可以在下一节中看到更多关于这个问题的信息。)
6.6对象节点
6、对象节点
对象节点是另一种能够存在于活动之中的节点,它会对对象令牌通过活动的流建模(其中对象令牌代表的是事件、能量或者数据的实例)。对象节点最常出现在两个动作之间,以表示第一个动作会产出对象令牌作为输出,第二个动作会将这些对象令牌作为输入。
对象节点的标识法是一个矩形。显示在对象节点中的名称字符串会拥有下面的格式:
〈object node name>: <type> [<multiplicity>]
对象节点名称是建模者定义的,类型必须与你在模型层级关系中某处定义的模块、值类型或者信号的名称匹配,它会指定对象节点能够持有的对象令牌类型。多重性会指定,在活动的执行过程中的特定时刻,对象节点能够持有多少个对象令牌。如果在名称字符串中没有显示,那么对象节点默认多重性为1..1。
图6.4中的活动片段显示了名为currentAltitude的对象节点。它持有代表值类型 km的实例的对象令牌。字符串末尾的多重性表示,第一个活动只会产出一个对象令牌作为输出,而第二个动作只需要一个对象令牌作为输人(以便启动)。
对象节点另一个有用的特性是,它可以选择显示分隔框一就像模块或者组成部分属性一样——来表示它持有的对象令牌的内部属性。当然,只有在指定的类型(模块、值类型或者信号)真正拥有内部属性的时候,这才真正有用。原始值类型,像 km,根本就不需要。
6.1、栓
栓(pin)是一种特殊类型的对象节点。你会把栓附加到动作上,表示动作的输入或输出。栓的标识法是附着在动作外边界上的小方块,如图6.5所示。
你可以选择在方块内部显示一个箭头,以指定栓代表的是输入还是输出。但如果你用边缘把两个栓连接在一起,那么这就是多余的,而大多数时候你都会这么做。栓的名称字符串格式和对象节点一样,但它会显示在栓的附近,而不是显示在栓中(原因显而易见)。
图6.5中的活动片段是图6.4中所显示的同一个模型的另一种视图。栓的意义和对象节点一样;栓只是你可以根据具体情况选择的另一种标识法,以满足看图者的特殊需求。每种方式都有优点和缺点。
对象节点标识法可以显示分隔框,以表示它所持有的对象令牌的内部属性。然而,它要比栓标识法占据活动图中更多空间。栓标识法无法显示分隔框,但这种标识 法更节省空间。作为规则,我建议选择栓标识法作为默认的选项,只有在一些不常见的场合,当你需要显示对象令牌的内部属性的时候,才使用对象节点标识法。
图6.6显示了输入栓和输出栓的例子,每个栓都拥有下限为零的多重性。这是你对拥有可选输入或输出的动作建模的方式。带有可选输入栓的动作甚至可以在没有任何对象令牌位于那个栓中的时候启动。带有可选输出栓的动作可以执行,并且在那个栓中可能不产出任何对象令牌。尽管那是冗余的,但SysML要求你,当栓的多重性下限是零的时候,(在名称前)使用<<optional>>元类型。
6.3、活动参数
活动参数是另一种特殊类型的对象节点。你会把它附加到活动图的外框上,从总体上表示活动的一种输入或者输出。活动参数的标识法是横跨在活动图外框上的矩形,如图6.7所示。活动参数的名称字符串的格式和对象节点(以及栓)相同。
SysML不会指定你必须在图外框的什么地方放置活动参数。然而,一种约定俗成的建模方法是把输入活动参数放在外框的顶部或者左边,输出活动参数放在底部或者右边。事实上,唯一能够告诉我们区别的决定性方式,就是连接在活动参数上的边缘的方向。
和栓类似,活动参数能够拥有的最低多重性是0。总的来说,这是你如何为活动建立一种可选参数的模型的方式。对于带有可选输入活动参数的活动(例如: sensorSelection),即便在那个参数中没有任何对象令牌,它也可以启动。带有可选输出活动参数的活动(例如:errorMsg)可以执行,并可能不会通过那个参数向触发活动的客户端返回任何对象。和栓一样,当活动参数的低多重性是零的时候,SysML 要求你在其之前(名称前面)引用<<optional>>元类型。
通过图6.6和图6.7中的例子,你可能会推断出,在栓和活动之间可能会有关联。后面会讨论那种关联的细节。
6.3、流与非流
默认情况下,动作和活动只有在执行的时候才会消费它们的输入对象令牌。类似地,只有在完成执行的时候,它们才会交付输出对象令牌。我们把这叫做非流 (nonstreaming)的行为。
然而,你所设计出来的系统行为不会总是如此,有时它们甚至会在行为持续执行的时候接受输入和产生输出。我们把这叫做流(streaming)行为。你可以在栓或者活动参数的名称字符串后面指定[stream],从而为流行为建模。
让我们首先看一下非流的情况。图6.8显示了一个动作,它带有一个非流的输入栓以及一个非流的输出栓。当类型为传输命令的对象令牌到达输入栓 currentCommand的时候,动作vc就会开始执行,并消费那个对象令牌。如果在vc 还在执行的时候,又有一个传输命令的实例到达输人栓,那么直到vc完成然后再次启动的时候,第二个实例才会被消费。
当vc执行的时候,它会在内部生成类型为Boolean的对象令牌,但是在vc完成执行之前,那个对象令牌都不会被发送到输出栓isCommandValid上。那样,在vc完成之前,任何跟随vc (并以那个Boolean值作为输人)的动作都不会开始执行。
那是非流的情况。现在让我们看一下流的情况。图6.9显示了一个动作ma,它拥有流输出栓currentAltitude。当ma执行的时候,它会在内部生成一个类型为km的对象令牌。那个对象令牌会发送到输出栓上,即便ma正在执行。这样,跟随ma (并要求km值作为输入)的动作都可以开始执行,即便ma仍然在持续执行(并可能产生其他对象令牌)。这是为两个或多个并行动作建模的一种方式。流在动作的输入端有着类似的意义。到达流输入栓的对象令牌会马上为动作所 用,即便它由于之前到达的对象令牌已经开始执行。
流和非流在活动参数的情境下和在栓的情况下有着同样的意义,只是应用给活动的规则有所不同。流栓和活动参数让你可以对持续的系统行为建模。
7、边
活动可以包含两种一般类型的元素:节点和边。上一节介绍了两种节点:动作和对象节点。本节会介绍两种边,你可以使用它们来连接节点,从而在活动中形成有序的序列:对象流和控制流。
7.1、对象流
对象流是一种边,它会传输对象令牌。使用对象流,可以表示事件、能量或者数据的实例通过活动,在系统操作过程中活动执行的时候,从一个节点向另一个节点流动。
对象流的标识法是带有箭头的实线。对象流一般会把两个对象节点连接在一起。
但是,除了对象节点之外,你还可以在对象流的一端拥有决定节点(decision node)、 合并节点(merge node)、分支节点(fork node)和集合节点(join node),来指示对象令牌的流。
图6.10显示了之前在图6.1中显示的大型活动图的节选部分。这个部分显示了七个对象流的示例。它们表示这个活动部分中的动作需要对象作为输入,并且会产出对象作为输出。
你必须确保对象流两端的对象节点拥有兼容的类型,在尾端作为输出产生的对象令牌必须可以作为箭头端的输入被接受。你可以以下面两种方式之一来满足这种约束:
- 1)类型可以是相同的(如图6.10所示)
- 2)上游的类型可以是下游类型的子类型(如图6.11所示)
图6.11中的活动图片段显示了图6.10中显示的行为的变种。Transfer Command 是Command的子类型(正如它们之间的泛化关系所传达的,那种关系显示在BDD 中)。而泛化关系表示有可置换性(意味着在需要超类的地方,子类都会被接受)。 动作vc需要Command类型的输入。因此它会接受它所有的子类型,包括Transfer Command的实例。
这是针对抽象设计的例子(例如:Command)。你应该对这种实践感觉良好,它在你的系统设计中创建了可扩展性,对于生命周期中稍后不可避免的变更来说,那会最小化成本。
7.2、控制流
控制流是一种传递控制令牌的边。控制令牌的到达可以启动等待它的动作。因 此,当活动中的对象流自身无法传达序列的时候,你会使用控制流来表示一系列动作之间的序列约束。
SysML允许我们对控制流使用两种标识法:带有箭头的虚线,或者带有箭头的实线。我建议,如果你的建模工具支持虚线的标识法,那么就使用它:那样,看图者很容易在活动图中区分控制流和对象流。
图6.12显示了图6.1中所示的大型活动图的一部分。这个部分显示了 7个控制流的示例,它们连接了活动中的节点,以定义它们之间排序的序列。那些节点中,有四个是动作:两个调用行为动作、一个等待时间动作以及一个接受事件动作。当一个动作完成时,它就会在输出的控制流中提供控制令牌,那会启动序列中的下一个动作。
8、再次阐述动作
既然你已经了解了对象节点和边,让我们看下,关于动作你还需要知道哪些内容。让我们看下动作什么时候开始,并讨论四种特别类型的动作:调用行为动作、发送信号动作、接受事件动作以及等待时间动作。
8.1、动作何时开始
为了正确地解释并创建活动图,知道动作什么时候会开始非常重要。一个动作想要启动,需要满足三个条件:
- 1)拥有动作的活动正在执行。
- 2)在所有输入的控制流上都有控制令牌到达。
- 3)在所有输入的对象流上都有足够数量的对象令牌到达,以满足相应输入栓的最低多重性。
让我们在几个示例动作的情境中看看这些规则,如图6.13所示。
动作ma拥有一个输入的控制流和一个输入的对象流。对象流附着在输人栓 sensorSelection上,它的最低多重性是零。因此这个动作在输入的控制流上有控制令牌的时候就启动,不管在栓sensorSelection上是否有对象令牌。
动作vc拥有一个输入的控制流和一个输入的对象流。对象流附着在一个输入栓上,而它需要Transfer Command类型的对象令牌。在这个栓上没有显示任何多重性。 因此它的多重性是默认的1..1。这意味着,当一个控制令牌和一个对象令牌分别到达相应的输入边时,这个动作就会启动。这些令牌不需要(也不太可能)同时到达。然而,想要让动作启动,两个令牌都必须出现在输入边上。
动作rs拥有一个输入的对象流,它附着在输入栓newAttitude上。这个输入栓是可选的,它的最小多重性是零。这个动作不需要任何令牌(任何类型)就能启动。在 这种情况下,只要满足三种条件中的第一个:只要拥有它的活动开始执行,这个动作就会启动。
建模者在绘制活动图的时候,常见的错误是向一个动作绘制多个输入边,以表达通向那个动作的不同路径。他们错误地相信多条输人边之间为“或”运算。正如你 '现在所知道的,这其实是“与”运算。所以请注意在你向自己的活动图中查找这样的错误,这是很容易犯的错误。如果你需要对通向特定动作的可选路径建模,就必须在活动之前插入一个合并节点。
你还要知道,动作可以不需要任何输入边。在那种情况下,你表达的是动作不会等待任何输人令牌,它会在活动启动的时候启动。如果在活动中有多个没有任何输入边的动作,那么就会同时启动。
8.2、调用行为动作
调用行为动作是一种特定的动作,它会在启用的时候触发另一种行为。调用行为动作可以把一个高层次的行为分解成一系列低层次的行为。
调用行为动作会根据刚刚讨论的相同规则启动。它所调用的行为可以是以下三种中的任意一种:交互、状态机或者其他活动。
调用行为动作的标识法和动作的标识法相同——圆角矩形——只是其中的名称字符串有其特别的格式:
<action name> : <Behavior Name>
动作名称由建模者定义。行为名称必须和你在模型层级关系中某处定义的交互、 状态机或者活动的名称匹配。
图6.13中所示的三种动作都是调用行为动作。你会发现Measure altitude、 Validate Command以及Rotate satellite是在系统模型中定义的三种行为的名称,所以你可以做出上述结论。调用行为动作(像ma和vc)的右下角出现分支符号,表示被调用的行为(Measure altitude、Validate Command)是一种活动。没有那种符号的调用行为动作(像rs)是模糊的;被调用的行为(Rotate satellite)可能是一种交互,也可能是一种状态机。尽管这么说,但是建模者很少使用调用行为动作来触发状态机。
图6.14显示了名为open telemetry stream的调用行为动作例子,它会触发行为 Stream telemetry data。分支符号表示Stream telemetry data是一种活动。这种调用行为动作有一个名为frame的输出栓,持有类型为Transfer Frame的对象令牌。因为这个栓是一种流栓(streaming pin),因此对象令牌可以从调用的活动中出现,并为这个栓所用——给下游动作消费——即便是在Stream telemetry data正在执行的时候。因此,这个动作可以在一次执行的过程中产生多个类型为Transfer Frame的对象令牌。
当调用行为动作触发另一个活动的时候,调用行为动作的栓必须与所调用活动的活动参数匹配。图6.15显示了 Stream telemetry data活动的定义,它拥有一个输出的活动参数,与图6.14中显示的输出栓相匹配。一旦这个活动被触发,它就会持续地把类型为Source Packet的对象令牌从多个数据源转换到类型为Transfer Frame的单个对象令牌流中。当每个对象令牌到达输出活动参数frame的时候,就会马上被传递到调用行为动作open telemetry stream相应的输出栓上。
你可以使用调用行为动作进行重构,把出现在多个地方的通用功能块抽取出来,在单独的行为中定义它,然后你只需要多次调用它。这种设计实践会让我们更易于重用那些通用的低层次行为。例如,Stream telemetry data活动包含三个调用行为动作,它们都会触发Create virtual channel frame行为。那个行为只定义了一次(在模型层级关系中的某处定义为活动),而在这里执行了多次。(在这种特定的情况下,那三次单独的执行并行发生。)
8.3、发送信号动作
为了满足可伸缩性和性能的要求,系统工程师经常会设计分布式和并发的系统。
这样的系统会使用并发机制来传递事件、能量和数据,并同步那些以并行的方式操作的各种动作。你可以使用发送信号动作和接受事件动作,在活动图上为这种类型的系统行为建模。
发送信号活动是一种特定类型的动作,启用的时候会异步地生成信号实例,并把它发送到目的地。发送信号动作会根据前文讨论的同样规则启动。
发送信号动作的标识法是形状类似于路标的五边形(如图6.16活动图中下半部分所示)。在发送信号动作(例如: Orbit Radius Updated)内部显示的字符串必须与在模型层级关系某处定义的信号的名称相匹配。
信号是一种模型元素,和模块一 样,信号可以拥有属性。那些属性一般会代表信号实例从发送方到目标方传递的数据。图6.16中的BDD显示了 Orbit Radius Updated信号。这个信号拥有一个属性: currentOrbitRadius,它的类型是km。因此这个信号的实例可以携带从发送方到目标方的一个km值。
图6.16中的活动图显示了图6.1中所示的Execute Hohmann Transfer活动的一部分,这部分重点在于发送信号动作。当这个发送信号动作启动的时候一 也就是在输入栓中有输入的对象令牌到达时——它就会异步地生成Orbit Radius Updated信号的实例,把currentOrbitRadius值传送给等待它的目标方。因为发送信号动作是异步的,所以不会等待来自目标方的回应。它会立即完成,并在输出边提供一个控制令牌。每当新的对象令牌到达其输入栓的时候,发送信号动作都 会启动。
8.4、接受事件动作
接受事件动作在异步行为中是发送信号动作的好搭档。接受事件动作是在活动中使用的元素,它表示活动在继续执行之前,必须等待发生一个异步的事件。一般情况 下,这个异步事件是接收信号实例。
接受事件动作的标识法是一个五边形,看起来像是一个矩形的一边有一个三角形的槽(如图6.17中活动图的左中部所示)。显示在接受事件动作中的字符串(例如: Orbit Radius Updated)通常会与你在模型层级关系某处定义的信号名称相匹配,表示接受事件动作会等待那个信号的实例,而该实例会异步到达。它到达,接受事件动作就会完成,控制流会前进到活动中的下一个节点。
图6.17显示了图6.1中所示的Execute Hohmann Transfer活动的一部分。这个部分关注发生在两个并行控制流之间,贯穿该活动的异步通信。并发的流会分别独自进行,但有时它们在行为的特定时间点必须是异步的。
在这个例子中,当控制令牌到达接受事件动作的输入控制流时,它就会启动,而那是在enter transfer orbit动作完成之后才会发生的事情。如果右侧的发送信号动作已经生成了一个Orbit Radius Updated信号实例,那么接受事件动作会立即完成,并在它的输出控制流中提供控制令牌,然后执行会进行到下一个节点。
另一方面,如果右侧的发送信号动作并没有生成Orbit Radius Updated信号实例,那么接受事件动作就会等待Orbit Radius Updated信号实例。Execute Hohmann Transfer动作在该信号实例生成之前都无法进行到enter final orbit动作。
和其他类型的动作一样,接受事件动作并不需要任何输入边。而前文的规则声明,对于没有输入边的动作来说,只要活动开始执行,它就会启动。在这方面接受事件动作也不例外,只要活动开始执行,没有输入边的接受事件动作就会启动,并开始监听信号实例。
然而,这里有一点区别:即便在第一个信号实例到达之后,没有输入边的接受事件动作也会保持有效,它会继续监听其他信号实例。通过这种方式,我们可以为持续响应异步事件的系统行为建模。
8.5、等待时间动作
等待时间事件发生的接受事件动作也叫做等待时间动作。等待时间动作的标识法是一个沙漏形状的符号,下面有时间表达式(如图6.18所示)。在等待时间动作和其他接受事件动作之间,区别仅在于标识法和事件的类型不同。在上一节中叙述的其他一切都对等待时间动作适用。
沙漏下面的时间表达式可以指定绝对时间事件,也可以指定相对时间事件。绝对时间事件表达式以关键字at开始——例如:at ( 1430 GMT)或at ( 14 NOV 2106, 1200 CST)或at ( transferStartTime)。相对时间事件表达式以关键字after开始, 例如:after ( 30 days)或 aftw ( 50ms)或 after (timerCount)。
图6.18中显示的活动片段是图6.1中显示的Execute Hohmann Transfer活动的 一部分。这个部分专注于活动中的等待时间动作,它会等待一个绝对时间事件异步发生。时间事件的特定值会放置在executionTime值属性中。在时间表达式中使用点标识法表示executionTime值属性嵌入在currentCommand对象中。
当控制令牌到达这个等待时间动作的输入控制流(上游动作完成,生成了有效的命令响应),它就会启动。如果绝对时间事件(由executionTime指定)已经发生,那么等待时间动作就会立刻完成,并在其输出控制流中提供控制令牌。这样执行过程就会前进到下一个节点。如果executionTime还没有出现,那么等待时间动作就会等待时间事件的发生。Execute Hohmann Transfer活动在那个时刻发生之前,都无法前进到 enter transfer orbit 动作。
如果等待时间动作拥有相对时间事件表达式(如图6.19所示)那么一旦等待时间动作启动,时间事件的时钟就会开始计时。因为这里显示的等待时间动作没有任何输入边,所以在Sample system temperature开始执行的时候,它就会启动。两秒之后,相对时间事件发生,等待时间动作会在其输出控制流中提供控制令牌,启动下游的调用行为动作,而它会调用Read temperature行为。
等待时间动作是一种特殊类型的接受事件动作。即便在所需要的事件第一次发生之后,也没有任何输入边的接受事件动作还会保持有效。因此,在图6.19中显示的等待时间动作会继续每两秒输出一个控制令牌(并触发Read temperature行为),直到 Sample system temperature活动终止。这个例子说明了如何为持续的周期性行为建模。
9、控制节点
前文已经讨论了能够在活动中存在的两种类型节点:动作节点和对象节点。现在让我们看下后一种节点:控制节点。使用控制节点,可以引导活动沿着路径执行, 而不只是简单的序列动作。控制节点既可以指引活动中控制令牌的流,也可以指引活动中对象令牌的流。
控制节点有7种类型:初始节点、活动最终节点、流最终节点、决定节点、合并节点、分支节点和集合节点。当然,可以使用这些节点的组合,在活动中定义任意复杂的控制逻辑,以满足系统功能需求。
让我们来看下每种节点是如何工作的。
9.1、初始节点
初始节点标记活动的起点。正式的说法是,它标记了活动中的一个位置,控制令牌的流会从那里开始。初始节点的标识法是一个小的实心圆形,它一般只有一个输出控制流。
图6.20中显示的活动片段是之前在图6.15中显示的活动Stream telemetry data 的一部分。这个片段关注三个并发的活动序列的启动,它们都从图顶部的初始节点开始。当Stream telemetry data活动开始的时候,会在这个初始节点上放置一个控制令牌。它会马上把输出控制流转换为分支节点,然后活动会继续执行。
也可能对象令牌流本身就足以在活动中定义正确的动作序列。对象令牌一般会从输入的活动参数(在活动图的外框上)开始。在这样的情况下,你不需要在活动中有初始节点。
9.2、流最终节点和活动最终节点
流最终节点和活动最终节点是标记控制令牌流结束的控制节点。然而,二者之间有明显的区别:当控制令牌到达流最终节点的时候,那个令牌会被销毁,以此标记单独一个控制流的结束。而当控制令牌到达活动最终节点的时候,整个活动都会结束, 以此标记所有控制流的结束(不管它们当前是否还在执行中)。
流最终节点的标识法是包含X的圆形。活动终节点的标识法是包含小的实心圆形的圆形。(之前展现的)图6.17显示了这两种节点的例子。
图6.17中显示的两个动作序列会并行执行。调用行为动作ma会在其输出栓上以流的形式持续输出对象令牌,ma下游的两个动作可能会因此执行多次。每次发送信号动作完成的时候,就会出现一个控制令牌,到达流终节点,并被销毁。活动中其他令牌都不会受到影响。Execute Hohmann Transfer活动会继续,直到enter final orbit动作完成,并输出一个控制令牌,它会到达活动终节点。那时,整个活动都会终止(即便活动ma还在输出对象令牌)。
9.3、决定节点
决定节点标记在活动中可替换序列的开始。其标识法是一个空心的菱形(如图6.21所示)。决定节点必须拥有单一的输入边,一般拥有两个或多个输出边。每个输出边会带有布尔表达式的标签,那叫做监听,显示为方括号中间的字符串(例如, 图6.21 中的 isCommandValid == False)。
当一个令牌——可能是对象令牌,也可能是控制令牌——到达决定节点的时候, 输出边的监听会被估值。令牌会提供给那时监听估值为真的输出边。
你有责任确保输出边上的系列监听完整且独立,从而确保每次令牌到达的时候,只有一个监听的值会是真。SysML允许你使用else作为(多)一个输出边的监听,以确保满足“完整”的标准。(如果所有其他监听的估值都是假,那么else的估值就是真。)
9.4、合并节点
合并节点标记活动中可选序列的结尾。其标识法和决定节点相同:空心菱形。你 可以通过输入边和输出边的数量来区分它们。合并节点拥有两条或多条输入边,而只拥有一个输出边。当一个令牌——可能是对象令牌,也可能是控制令牌——通过任意一条输入边到达合并节点,令牌马上就会提供给输出边。
常见的情况是,合并节点和决定节点组合使用,在活动中对循环建模(如图6.22 所示)。事实上,合并节点对于建立循环模型来说非常重要。如果这幅图中的合并节点 被删除(它的两条输入边直接与接受事件动作连接),那么接受事件动作就永远都不会启动。为了开始,它需要在每条输人边上都有控制令牌,这在循环中是永远都不会发生的。
你还可以使用合并节点来建立令牌交错情况的模型,这些令牌来自多个并发的来源,你需要把它们合并到单一的输出流中(如图6.23所示)。在这个活动中,触发 Create virtual channel frame活动的三个调用行为动作会并发执行。它们会以彼此独立地输出类型为Virtual Channel Frame的对象令牌,那些令牌会以不确定的顺序到达合并节点。每个令牌到达后会马上提供给输出边,并成为下一个动作的输入。然后, 那个动作会输出类型为Transfer Frame的单个对象令牌流。
9.5、分支节点
分支节点标记活动中并发序列的起点。分支节点的标识法是一条线段(方向随意),它必须拥有一条输入边和两条或多条输出边(如图6.24所示)。当一个令 牌——可能是对象令牌,也可能是控制令牌——到达分支节点的时候,它会被复制到所有输出边上。原始令牌的每个副本都代表独立、并发、沿着各自路径前进的控制流。
图6.24中显示的活动片段是之前图6.1中所示的Execute Hohmann Transfer活动的一 部分。这个部分关注活动中的分支节点。控制令牌到达分支节点后会被复制到两条输出边上。然后那两个副本会向前执行到各自的下游活动vc和ma上。动作ma会马上启动, 它不会等待任何其他输人。而动作vc会在类型为Transfer Command的对象令牌到达其输入栓的时候启动。启动的时间满足一定条件时,两个动作可能在一段时间内并行执行。 关于并发很重要的一点是,并发动作完成的顺序是不确定的。你无法事先知道,在系统操作特定的Execute Hohraann Transfer活动中,vc和ma哪一个会首先完成。 更准确的说法是,哪个先完成没什么关系,只有在它们彼此之间没有依赖关系的时候,你才能把动作建立为并发的模型。
9.6、集合节点
集合节点标记活动中并发序列的结束。集合节点的标识法和分支节点一样:一条线段。你可以通过输入、输出边的数量来区分它们。集合节点一般拥有两条或多条输 人边,而只有一条输出边(如图6.25中活动图的底部所示)。
你可以使用集合节点为活动中并发动作序列的同步点建模。当令牌到达每条输入边的时候,就会有单个令牌提供给输出边。并发序列结束,一条控制流会通过集合节点所标记的点,继续执行。
10、活动分区:把行为分配给结构
活动图不仅可以传达活动中动作的顺序,而且还可以传达执行每个动作的结构。 你可以使用活动分区来表示。活动分区的标识法是一个大矩形(包含一个或多个节点),在一端有头部内容,头部内容会指定活动分区代表什么。活动分区的方向可能是水平的,也可能是垂直的,但是某些建模工具可能只支持其中一种方向。
活动分区最常见的情况下会代表存在于系统模型某处的一个模块或者一个组成部分属性。在活动分区中放置一个动作表示该动作被分配给一个名称由头部决定的结构。如果只是简单地放置,那么当活动在系统操作过程中执行的时候,结构就会负责执行那个动作。
当活动分区代表一个模块的时候,它表示那个模块的所有实例都能够执行其中包含的动作。当活动分区代表组成部分属性的时候,那么只是那个组成部分属性会负责执行所包含的动作。
图6.26提供了图6.1所示的完整Execute Hohmann Transfer活动的另一种视图。
这里显示的动作和之前完全一样。但是,这个视图还显示了三个活动分区,表示那些动作被分配给系统模型的三个不同的模块:Microcosm Autonomous Navigation System ( MANS)、Propulsion Subsystem 和 Flight Computer。这个视图表现的 Execute Hohmann Transfer更容易理解——这种表述把行为与结构相连接。
11、小结
活动图是一种强大的信息沟通媒介,你可以随着时间的推移使用它与利益相关者沟通系统的行为。当你需要显示持续的系统行为,并且需要关注事件、能量和数据的流在一系列动作之间的流动(可能是串行或者并行)时,这种图是很好的选择。活动图的重大优势在于它的可读性,即便所显示的行为拥有复杂的控制逻辑。
你可以在活动中创建调用行为动作来为行为分解建模。发送信号动作和接受事件动作让你可以为分布式系统中结构之间的异步通信建模。你可以使用等待时间动作为周期性发生的行为或者在特定时间点发生的行为建模。活动分区让你可以为活动中的动作分配职责,以指定系统中的结构。所有这些特性都让活动图成为表达系统行为的—种意义丰富的媒介。