SysML 序列图(Sequence Diagram)
SysML序列图是另一种可以用来说明系统动态行为信息的图。你可以使用叫做生命线的元素,为系统行为中的参与者建模,然后使用生命线之间的消息,为那些参与者之间的交互建模。你可以指定交互的时间约束和持续期间约束。还可以使用各种交互操作符来指引交互的执行。使用交互可以对一系列交互之间的行为分别建模。
1、目的
序列图是一种行为图。和活动图一样,它表示了系统的一种动态视图,这种视图会说明随着时间推移而发生的行为和事件的序列。模块的各个部分会通过操作调用和异步信号彼此交互,以产生浮现式的行为,当你关注上述内容时,使用序列图就是很好的选择。这种行为的正式叫法是交互(interaction)。
SysML让你可以使用三种类型的行为图来说明行为:序列图、活动图和状态机图。每种图都有其自身的优点和缺点,你应该知道如何基于看图者的需求来做出佳选择。
序列图是对行为的精确说明,因此它很适合用在详细设计方面——那会作为开发的输入项。很多商业化建模工具模糊了设计和开发之间的界限,它们让你可以基于显示在序列图中的交互自动生成生产环境质量要求的源代码。这是可能实现的,因为序列图会表达自动转换成源代码所需的三种前提信息:行为执行的顺序、哪个结构会执行哪种行为,以及哪个结构会触发哪种行为。
然而,精确度和可读性不可兼得。随着行为中控制逻辑复杂度的增加,序列图很快就会变得不可读。因此,建模者通常会使用序列图作为图形化的测试案例说明书, 只用它显示大型(更复杂)用例行为的单一场景。然而,我们不应受限于这种习惯性用法。序列图是一种意义非常丰富的媒介,可以针对一般目的的行为说明工具。
2、何时创建序列图
因为序列图是一种万能的行为图,所以你可以通过创建它来说明系统层级关系中任意级別的行为。序列图在系统生命周期早期非常有用,在ConOps开发阶段,序列图可以说明关注系统与其环境中的执行者之间可能发生的交互。这些图在架构设计阶段早期会很有用,可以说明子系统之间的交互。它们在架构设计末期也很重要,可以说明组件之间的交互,从而准备好可以分发给组件详细设计团队的文档。
如果你的团队选择使用序列图作为图形化的测试案例说明,那么你还可以创建序列图来替换基于文字的传统测试案例说明。在生命周期创建它的具体时间会因为团队不同而有很大差別。例如,测试驱动设计和开发的团队会在创建测试案例说明的同时创建需求说明(或者用例说明)活动。
当你需要精确地指定实体之间的交互、系统问题域内的交互或者解决方案域内的交互的时候,序列图就是非常好的一种选择。简而言之,你可以在系统生命周期的任意时间点创建序列图。
3、序列图外框
序列图的图类型缩写是sd。在序列图中唯一允许使用的模型元素就是交互。序列图的外框总是表示你在系统模型某处定义的一个交互。
交互本身是一种模型元素。和活动一样,它是一种行为。和活动、模块和包一样,交互也是一种命名空间。因此,它可以在模型层级关系中包含一系列命名的元素(像生命线、事件发生和消息)。那些被包含的元素可以出现在相关序列图的外框之内。
图7.1中图的头部告诉我们,这幅序列图的外框代表名为Execute Hohmann Transfer, Main Success Scenario的交互,它在系统模型中的某处已经定义。显示在外框内部的元素都包含在模型层级关系中的这个交互之中(嵌套在其中)。
图7.1 中的序列图显示了Execute Hohmann Transfer用例说明中的主要成功场景一当卫星接收有效的传输命令时,那个行为就会发生。因为这两幅图显示了类似的行为,所以我们可以看出序列图和活动图在可读性方面的比较。
正如在本章中之前所提到的,序列图的精确度和可读性不可兼得。图7.1中的序列图非常杂乱。本文会分析这幅图,详细说明能够出现在序列图中的各种元素。
4、生命线
生命线是代表交互参与者的一种元素(见图7.2)。更准确的说法是,生命线代表交互中参与者的单一实例,它会与其他生命线交换数据。显示特定交互中的生命线和拥有该交互的模块的组成部分属性相关。那个模块可能是我们感兴趣的系统、一个子系统或者一个组件。
生命线的标识法是一个矩形,附着有虚线,在序列图中显示为上下方向。虚线代表组成部分属性的生命(和交互中的参与者相关,不一定在操作的系统中一直存在)。 时间会沿着生命线向下进行,先发生的事件会显示在生命线中比较高的位置,而后发生的会显示在比较低的位置。
然而,序列图只表达事件发生的时序。生命线上两个事件发生之间的距离没有任何意义,有意义的只是哪个更高一些,哪个更低一些。
矩形指的是生命线的头部。它包含了一个名称字符串,可以标识生命线所代表的组成部分属性。名称字符串的格式如下:
<part property name> [<selector expression>] : <type>
已命名的组成部分属性的类型是一个模块或者执行者,你在模型层级关系中的某处已经对其进行了定义,常见的是模块。然而,如果你的团队选择使用序列图显示领域级別的交互(在系统和环境中的执行者之间),那么你就会拥有类型为执行者的生命线。
选择器表达式是名称字符串的可选部分。在显示的时候,它会出现在紧挨着生命线字符串后的方括号之中。选择器表达式会指定生命线代表的特定实例。
这个概念需要详细解释一下。生命线代表单个实例,而生命线的名称必须与组成部分属性的名称相关。单个组成部分属性可能代表一整个实例集合(如果组成属性的多重性大于1)。记住这一点,选择器表达式指定(在一个集合中)生命线代表的特定实例。
图7.3右边的BDD显示了 Software Subsystem模块拥有一个名为ssdd的组成部分属性,它的类型是Star Sensor Device Driver。这个组成部分属性的多重性是3,表 示它代表的是Star Sensor Device Driver的三个实例,因此ssdd组成部分属性代表的是实例的集合。图7.3中的序列图拥有名为ssdd的生命线,它和名称相同的组成部分属性相对应。生命线指定了一个选择器表达式x-axis,它是这个交互中生命线代表的 ssdd集合中的特定实例。
然而,正如之前所提到的,选择表达式是可选的。只有在它真正能够影响集合中的某个实例参与到交互中的时候,你才需要在生命线上添加一个。如果你忽略了选择器表达式,生命线就会表示任意实例,表示哪个实例实际参与到交互中,都无关紧要。
在前面的内容中,我提到生命线表达了时间的流逝(沿着图向下)。然后我引用了事件发生的概念,它出现在生命线上。关键是,交互中的系列生命线会表示发生事件的序列,那些序列会形成对交互的描述。
在生命线上可以出现6种类型的事件:
- 1)消息发送事件
- 2)消息接收事件
- 3)生命线创建事件
- 4)生命线销毁事件
- 5)行为执行开始事件
- 6)行为执行结束事件
下一节会讨论如何在序列图中表示各种类型的事件,以传达有意义的描述。
5、消息
消息代表的是发送生命线和接收生命线之间的通信。那种通信可能是启动行为、 在行为的末尾发送回应、创建生命线或者销毁生命线。
消息的标识法一般是带有箭头的线,连接发送和接收生命线。消息的尾端与发送生命线连接,消息的箭头端与接收生命线连接。每种类型的消息都有其特殊的线形 (像虚线或实线)和箭头形状(像开口箭头或者实心箭头)。
消息上而还会有一个指定消息名称的字符串,它会和其他可选的消息片段(参数名称、参数值和返回值)在一起。名称字符串的具体格式取决于消息的类型。
常见的情况下,发送生命线和接收生命线是两条不同的线。不过,一条生命线可以同时是消息的发送方和接收方。图7.1中显示了四个这样的例子。这可能表示生命线给自己发送了一个消息(例如,为了触发内部行为),也可能表示生命线是一个实体的黑盒,那个实体由相互通信的内部组成部分构成。
当系统工程师使用术语消息的时候,它通常意味着某种数据格式。请不要把这种倾向带到对SysML的学习中。在交互的情境中,术语消息并没有这种意义。例如, 两条生命线之间的消息可能代表对操作的调用,即便那个消息并没有传入任何数据。 当然,你可以表示数据(或者更一般的说法:对象)在生命线之间传递(通过在消息的名称字符串中显示参数)。然而,术语消息永远不会有这种意义。
消息事件
5.1、消息事件
在交互的生命线上可以有六种事件发生。其中两种是消息发送事件和消息接收事件。我们可以把这两种更一般化地称为消息事件。
每次你创建从一条生命线到另一条的消息时(或者从一条生命线到其自身),你就同时为消息发送事件和消息接收事件建模了。在消息的尾端与生命线相交的地方,就会存在消息发送事件;在消息的箭头端与生命线相交的地方,就存在消息接收事件。 简而言之,消息事件没有任何标识法,它隐藏在消息末端和生命线相连的地方。
图7.4中的序列图是图7.1中大型交互的节选部分。这个部分显示名为fc的生命线向名为mans的生命线发送measureAltitude消息。在fc生命线与measureAltitude消息的尾端相交的地方存在一个消息发送事件,而在mans生命线与measureAltitude 消息的箭头端相交的地方存在一个消息接收事件。
图7.4中的measureAltitude消息恰好是一个异步消息(我会在下一节详细讨论)。 然而,对于其他消息类型,此处讨论的所有关于消息事件的观点都同样适用。
5.2、消息类型
一般在交互中会出现四种类型的消息:异步消息、同步消息、回复消息和创建消息。(SysML还定义了两种其他消息类型——找到的消息和丢失的消息——但是你在日常实践中很少会用到它们。)每种类型的消息都有独特的标识法。在大型交互的情境中每种类型为不同的目的服务。让我们来详细看下各种消息。
5.2.1、异步消息
异步消息代表的是发送生命线和接收生命线之间的一种通信,而发送方会在发送消息之后马上继续执行。发送方不会等待接收方完成被触发的行为,也不会等待接收 方在完成行为的时候发送回应。
异步消息的标识法是带有开口箭头的实线(从发送生命线画向接收生命线),在线上有一个标签,格式如下:
<message name> ( <input argument list>)
消息名称必须与接收生命线所拥有的接收的名称匹配。接收是模块可以拥有的一种行为特性——一种总是会被异步触发的行为特性。
输入参数列表是可选的信息片段。如果你选择不显示它,那么就可以在消息名称之后显示一组空括号——或者什么都不显示。如果你选择显示参数,那么就要以逗号分隔的列表形式显示,列表中的每个参数都会遵循下面的格式:
<input parameter name> = <value specification>
输入参数名称是可选的;如果显示的话,那么它必须与被触发的接收的输入参数名称匹配。常见的情况下,建模者会忽略这条信息,只显示一个值,可能是一个数值,或者是持有一个值的属性名称。当然,所传递的值必须与相关输入参数的类型相匹配(例如:Integer、Real、Boolean、String、Complex,或者自定义的值类型,或者你在模型某处定义的模块)。
图7.5中的序列图显示了图7.1所示交互的一部分,在fc生命线上还有一些额外的信息,并没有显示在原始的交互上。此处显示的交互片段包含三个异步消息: measureAltitude、currentAltitudeUpdated 和orbitRadiusUpdated。measureAltitude 消息和 currentAltitudeUpdated 消息分别与 Microcosm Autonomous Navigation System (MANS)模块所拥有的一个接收相关,而orbitRadiusUpdated消息与Flight Computer 模块拥有的接收相关。
measureAltitude消息中没有显示任何参数列表(可能是因为measureAltitude接收没有任何输入参数,或者是因为这个消息的参数对于看图者来说并不重要)。然而,currentAltitudeUpdated消息显示了一个参数:名为current Altitude的属性。这个属性所持有的值是接收的输入。当接收生命线执行与currentAltitudeUpdated接收相关的行为时,就可以访问那个值。
类似地,orbitRadiusUpdated 消息显示了一个参数:名为 currentOrbitRadius 的属性。mans生命线把消息中的这个值传递给了 fc生命线。这样fc生命线就可以在执行与orbitRadiusUpdated消息相关的行为时,访问那个值。
在之前的内容中,我提到建模者经常会创建序列图作为图形化测试案例说明。这时,数值(例如: 35786 )比较适合作为消息参数,而属性名称(例如: currentAltitude)就不太适合。贯穿交互的系列输入值定义了一种特殊的测试案例, 并且可以让你决定(并指定)一系列期望的输出值。然后,你可以把那个序列图与实际执行(或模拟)的测试结果相比较,以得出结论(成功还是失败)。
关键是:异步消息的发送方不会等待接收方完成被触发行为的执行,发送方会立刻继续执行。例如,在图7.5中,fc向mans发送了 一条异步消息 measureAltitude。在接收到那条消息之后(或者在那之后的某个不确定的时间点), mans开始执行与measureAltitude接收相关的行为。然而,fc生命线不会等待行为结 束。在发送了 measureAltitude消息之后,fc马上会继续执行,并发送另一条消息 checkSensorStatus。
如果measureAltitude是一条同步消息,那么fc生命线就需要等待mans完成 measureAltitude行为的执行,并向fc发送响应,然后fc才可以继续发送checkSensorStatus 消息。现在我们来详细讨论一下同步消息。
5.2.2、同步消息
同步消息代表的是发送生命线和接收生命线之间的一种通信,其中发送方会等待接收方完成被触发行为的执行,发送回复消息,然后才会继续自身的执行。同步消息的标识法是带有实心箭头的实线(从发送生命线画向接收生命线)。同步消息的标签和异步消息的标签格式相同:
<message name> ( <input argument list>)
不过,消息的名称必须与接收生命线所拥有的操作的名称匹配。
输入参数列表仍然是可选的。如果你选择显示参数,那么每个参数的格式都和之 前一样:
〈input parameter name>= <value specification>
我对异步消息的参数的一切说明,对于同步消息同样适用。但值得重复的是,这个字符串的值说明部分可以是一个数值,也可以是持有值的属性的名称。(正如我之前提到过的,当序列图作为图形化测试案例说明的时候,数值会比较适合作为消息参数。)
图7.6中的序列图显示了图7.1所示交互的一部分。此处显示的交互片段包含两个同步消息,代表对fireThrusters操作的两次调用,而该操作属于Propulsion Subsystem 模块。
这些消息没有显示任何参数列表。然而,异步消息的关键点在这里同样关键。当接收生命线执行与被触发的操作相关的行为时,所有在同步消息中传送过来的参数都是可以访问的。
同步消息的关键点在于,消息的发送方会等待接收方完成被触发行为的执行,然后再继续自身的执行。例如,在图7.6中,fc会把第一个fireThrusters同步消息发送给ps。在接收到那个消息之后(或者在之后某个不确定的时间点),ps会开始执行与 fireThrusters相关的行为。fc生命线必须等待那个行为完成(并且必须等待ps发送标志着行为已经完成的回复消息),然后才会继续自身的执行,并在之后的某个时间发送另一个 fireThrusters 消息。
你可能想知道在两条fireThrusters消息之间,fc生命线上面的字符串是什么。那个字符串代表状态常量。后面会对其加以详细讨论。
5.2.3、回复消息
回复消息代表一种标记同步调用行为结束的通信。它总是(通过交互中早期的同步消息)从执行行为的生命线发送到触发行为的生命线。
回复消息的标识法是带有开口箭头的虚线。图7.6显示了两个回复消息的例子—— 每个都跟在相应的同步消息之后。你可以选择在回复消息上显示一个标签,格式如下:
<assignment target>= <message name>
( <output argument list>) : <value specification>
消息名称必须与相应同步消息的名称匹配(那也是同步消息所触发的操作的名称)。 冒号后面的值说明代表刚刚完成执行的行为的返回值。但是,只有在被触发的操作真的拥有声明的返回类型时,这段字符串才会出现。
赋值目标是可选的信息。如果显示的话,它表示捕获返回值的属性名称——触发操作的生命线拥有的属性(也就是接收回复消息的那个)。然而,建模者一般会忽略这个信息。
输出参数列表也是可选的。如果你选择显示参数,那么它们就会以逗号分隔的列表形式显示,每个参数都会遵循下面的格式:
<output parameter name>: <value specification>
输出参数的名称是可选的;如果显示的话,它必须与被触发操作的输出参数名称匹配。建模者通常会忽略这条信息,只显示发送回调用者的值说明,作为被触发操作的输出。
注意,回复消息显示本身是可选的。图7.7中显示的交互片段和图7.6中显示的交互片段是等价的。建模者通常会忽略回复消息,以节省序列图中的空间。但是,为 了更加清晰,回复消息仍然会发送回调用方。当它不显示的时候,只是被隐藏了。发送同步消息的生命线在完成被触发行为的时候,总是会接收一条回复消息,即便那条回复消息并没有显示在序列图上。
5.2.4、创建消息
创建消息代表在系统中创建新实例的通信——之后会参与到交互中的一个实例。 创建消息的标识法是带有开口箭头的虚线。消息的尾端与发送生命线连接(和平常一 样)。消息的箭头端会和被创建的生命线的头部相连。
正如我所提到的,在生命线上可以出现六种不同类型的事件。到现在为止,我已经讨论过两种:消息发送事件和消息接收事件。现在我要推出第三个:生命线创建事件。生命线创建事件会存在于创建消息与生命线头部的交点处。(在那里也会存在消息接收事件,因此它和生命创建事件是同时发生的。)
图7.8中的序列图显示了 Initialize MANS, Main Success Scenario交互。这个交互包含一个创建消息,它从fc生命线画向新生命线mansdd的头部。这表示在交互的 这个点上会创建MANS Device Driver的新实例。然后mansdd生命线会在创建事件之后接收和发送消息(即参与到交互中)。
6、析构事件
能够出现在生命线上的第四种事件是生命线析构事件(或者简称为析构事件)。 析构事件代表生命线的结束,并在生命线所代表的系统中析构该实例。
SysML没有在任何特定的情境下定义“析构”。对于软件对象,析构一般指的是一种动作,它会释放分配给对象的内存。对硬件对象来说,析构可能指的是从系统中移除一个对象,或者销毁一件真实的物理对象(像是可以从午餐车上拆下存储仓的电焊设备)。
析构事件的标识法是X形状的叉,位于被销毁的生命线底部(见图7.9)。X可能会显示在生命线的底部,不附加任何消息。这表示在交互的该时间点,生命线已经结束。
X还可能会与消息的箭头端连接。这表示析构事件是生命线接收特定类型消息的结果,这个特定类型消息叫做删除消息。SysML (和UML)规范都很奇怪,没有说明删除消息所需要的线形以及箭头风格。语言只是规定删除消息必须在析构事件中结束。
图7.9中的序列图显示了 Shut Down MANS、Main Success Scenario交互。这个交互在mansdd生命线的底部包含析构事件。这个特定的析构事件是mansdd生命线 从fc生命线接收uninstall消息的结果。这个析构事件表示MANS Device Driver模块的实例会在交互的那个时间点被销毁。析构事件是能够在生命线上出现的后一个事件。因此,mansdd生命线在其析构事件之后的交互中,既不能发送消息,也不能接收任何消息。
7、执行说明
能够出现在生命线上的后两种事件类型是行为执行开始事件和行为执行终止事件。行为执行开始事件一般隐藏在生命线接收同步或异步消息的地方。行为执行终止事件一般隐藏在生命线发送回复消息的地方。
然而,通常情况下,显式地表示行为在生命线的何处开始和结束,会消除不明确的情况,因此非常有用。SysML提供了一种可选机制来做到这一点:执行说明。
执行说明的标识法是一个狭窄、垂直的矩形——可能是空白的,也可能带有填充图案——在生命线执行一个行为的时候,它会在交互的一段时间内覆盖生命线。矩形的顶部会显式地标记行为执行开始事件。矩形的底部会显式地标记行为执行终止事件。
尽管这不是语言所要求的,但行为执行开始事件通常会和消息接收事件同时发生。 因此,你一般会看到同步或者异步消息的箭头端和执行说明的顶部连接。类似地,行为执行终止事件一般会和发送回复消息同时发生,所以你会看到回复消息的尾端和执行说明的底部连接。(但是,当然,这只对由同步消息而不是异步消息触发的行为适用。)
图7.10显示了图7.8所示交互的另一种视图。在此显示了执行说明,从而显式地传达三条生命线何吋开始和结束行为的执行。fc生命线会在一段时间内执行 initializeMANS行为,从接收initializeMANS消息开始,一直到发送相应的回复消息。
当initializeMANS行为正在执行的时候,fc生命线会创建mansdd生命线,并发 送一条initialize同步消息,使相应的行为在mansdd生命线上开始执行。这种行为会 在mansdd生命线发送回复信息给fc生命线的时候结束执行。
当initialize行为在mansdd生命线上执行的时候,那条生命线会发送两条同步消息 initialize和calibrate 给mans生命线。mans生命线接收到这些消息,会使相应的行为开始执行。
注意,当那些行为完成执行的时候,并没有显示任何回复消息 。 前文讲过, 显示回复消息是可选的;不显示的时候,回复消息会隐藏在通过同步消息触发的行为的末端。
有时候,一条生命线会在一个外部行为的情境中执行内嵌的行为。你可以使用小型的执行说明来表达这一点,它会与同一条生命线上的更大的执行说明部分重叠。 图7.1中的交互显示了这样的例子。
8、约束
序列图可以指定各种类型的约束。约束是一个布尔表达式,一般显示在大括号中,并且能够出现在各种类型的SysML图中。在交互情境的日常实践中可以使用三种约束:时间约束、期间约束和状态常量。
8.1、时间约束
图7.11显示的是图7.1中大型交互的一部分。这个交互片段对于种不同类型的约束都包含了至少一个例子。
图7.11中的约束{currentCommand.executionTime}是一个时间约束的例子。时间约束会指定单个事件发生所需要的时间间隔。那个时间间隔可能是单独的时间值 (也就是说,最大值和最小值都一样),也可能是持有一个时间值的属性。而它所连接的事件发生可以是前文列举的6种类型中的任意一种。
关键是:当交互在系统操作过程中执行的时候,只有那个事件发生在时间约束指定的时间间隔中,我们才能够认为它能有效地执行。
图7.11中的时间约束附着在fc生命线上的消息发送事件fireThrusters之上。这表示只有fc在executionTime属性(它是currentCommand属性的一部分,用点表达式 来表示)持有的时间点发送第一个fireThmsters消息,这个交互的执行才能认为是有效的。
8.2、期间约束
图7.11中的两个约束{2min..5min}是期间约束的例子。持续期间约束会指定两个事件发生所需的时间间隔。同样,这里的时间间隔可能是单独的时间值,也可能是持有吋间值的属性。与之连接的一对事件发生可以是前文所列举的六种中的任意两种,。
关键是: 当交互在系统操作中执行的时候,只有那一对事件发生相隔的时间恰好落在期间约束所指定的时间间隔中,我们才能够认为它们是有效的。
图7.11中的每个期间约束都连接到fireThrusters行为的行为执行开始事件和行为执行结束事件上,那个行为会由ps生命线来执行。这表示,只有ps执行 fireThrusters行为持续的时间在两分钟到五分钟之间(每次行为被触发的时候),这个 交互的执行才能够认为是有效的。
还有一种常见的做法是,把期间约束应用在消息发送事件和相关的消息接收事件 上。这会限制消息的传输时间。当为此应用期间约束的时候,你需要把它放在大括号之中,然后显示在约束消息的上面或下面。
8.3、状态常量
图7.11中下面这个约束是状态常量的例子:
状态常量是一个条件,你可以在特定的事件发生之前(紧挨着的上面)指定给特定的生命线。在交互的有效执行中,那个条件在那个事件发生的时间点上必须为真。
图7.11中的状态常量应用给了fc生命线,位于第二个fireThrusters消息发送事件之前。这表示只有在currentOrbitRadius属性中持有的值和orderedOrbitRadius中持有的值在fc发送第二个fireThrusters消息的时候相等,我们才认为这个交互的执行有效。
你还可以使用状态标识法(圆角矩形)来说明状态常量,该标识法一般会出现在状态机图中。和之前一样,这个标识法会在特定事件发生之前出现在特定的生命线上。然而,如果使用这种形式,状态常量不会包含布尔表达式;它只会包含状态的名称,生命线在事件发生的那个时刻必须处于那种状态。然而,只有模块(它的名称显示在生命线头部的冒号之后)真正拥有一个状态机行为,并且该行为拥有定义好的一 系列状态,这种标识法才有意义。
图7.12中显示的交互片段是图7.11中显示的交互的变体。在这个变体中,Flight Computer模块拥有一个状态机行为(在模型层级关系的某处定义),这个行为包含一 种名为At Transfer Orbit Apogee的状态。这种状态的名称在fc生命线的状态常量中定义,表示当生命线发送第二个fireThrusters消息的时候,必须处于那种状态。如果不是那样,那么交互的执行就是无效的。
9、组合片段
组合片段是一种机制,让你可以向交互添加控制逻辑(像决定、循环、并发行为)。组合片段的标识法是一个矩形,它会出现在序列图外框内的某处。矩形会放在一个或多个生命线之上,并封装在那些生命线之间传递的一条或多条消息——由那个组合片段定义的控制逻辑所决定的消息。
你会使用在矩形左上角的分隔框中显现的字符串来指定控制逻辑。我们把那个字符串叫做交互操作符。SysML定义了 11种交互操作符,有四种你可能会在日常建模工作中用到,分别是: opt、alt、loop和par。我们在接下来的章节中详细讨论它们。
在我们讨论那些细节之前,你应该知道,每个组合片段都由一个或多个交互操作数(简称操作数)组成。操作数在组合片段中以区域的形式显示——区域由虚线分隔, 那条线会水平穿过矩形。组合片段中的每个操作数(每个区域)包含一条或多条消息, 它们的出现与否基于该组合片段定义的控制逻辑。在接下来的小节中看到具体的组合片段例子时,你会更加清楚。
接下来的四节会分别讨论四种常见的交互操作符类型。在此之前要知道,你可以把组合片段嵌入到其他组合片段中,以创建任意复杂的控制逻辑,这很重要。在之前显示的图7.1中你可以看到示例。
9.1、opt操作符
带有opt交互操作符(在左上角)的组合片段代表一系列可选的事件,如果条件——称为守卫(guard)——的估值为真,那么就会在交互的执行过程中发生。opt组合片段只会拥有一个操作数(区域),所以你不会看到水平穿过矩形的虚线。在那个操作数中的事件要么发生,要么不发生(基于系统操作过程中守卫的估值)。
守卫是一个布尔表达式,放在一对方括号之间,显示在opt组合片段的顶部附近。你必须把守卫放在组合片段中第一个事件发生的生命线上。所有出现在布尔表达式中的属性都必须是那个生命线的属性,或者是总体上拥有交互的模块的属性。
图7.13显示了图7.1所示大型序列图的一部分,它关注opt组合片段。这个组合片段包含很多事件——特别是消息发送事件、消息接收事件、行为执行开始事件和行为执行结束事件。在这个交互的执行过程中,如果守卫isCommandValid==True的估值为真,那么这一系列被包含的事件就会成为执行的一部分。如果守卫的估值为假,那么被包含的事件就会完全被跳过。
守卫包含了属性isCommandValid。它或者是fc生命线的属性(它会与Flight Computer模块的属性相对应)或者拥有该交互的模块的属性(它并没有在序列图上出现)。你可以在BDD上传达这些额外的信息。
9.2、alt操作符
带有alt交互操作符的组合片段代表两个或多个可替换的系列事件,它们会在 互的一次执行中发生。alt组合片段必须拥有两个或多个操作数(区域),其中包含那些可替换的系列事件。正如之前所提到的,每个操作数都会由水平穿过矩形的虚线分隔。
alt组合片段中的每个操作数都拥有自己的守卫。只有一个守卫的估值可以是真, 在那个守卫下的操作数中的系列事件会成为执行的一部分;在所有其他操作数中的事件都会被完全跳过。你的职责是要确保alt组合片段中的系列守卫都是互斥的。如果多个守卫可以同时为真,那么我们就可以认为你的模型有问题(比较学术的说法是:你没有遵循SysML的规则)。
你可以为(多)一个操作数使用预定义的守卫else。只有所有其他守卫都为假的时候,这个守卫才能为真。但是你并不一定要使用else守卫。可能(也允许)没有任何一个守卫为真(这种情况下,会跳过整个alt组合片段)。
图7.14显示了 Initialize MANS交互,它既包含主要成功场景(第一次显示在 图7.8中),还包含一个错误场景。这个交互包含一个alt组合片段,为那两个场景不同的结束情况建模。
当mansdd生命线向fc生命线发送initialize回复消息的时候,那个消息的返回值会存储在sensorStatus属性中(它可能是fc生命线的一种属性,也可能是拥有交互的模块的属性)。然后,这个属性会被读取,以对alt组合片段第一个操作数中的守卫估值。如果那个守卫为真,那么fc生命线就会发送initializeMANS回复消息,它的返回值是Ready (在另一个操作数中的所有事件都会跳过)。如果那个守卫为假,那么 else守卫的估值会是真,第二个操作符中的事件会成为执行的组成部分。
9.3、loop操作符
带有loop交互操作符的组合片段代表一系列事件,它们可以在交互的一次执行过程中发生多次。和opt组合片段一样,loop组合片段也只有一个操作数(区域)。
你可以指定loop的min.和max.迭代次数,把它放在紧挨着loop交互操作符右侧的括号中。这个范围会以下面这样的格式指定:
(<min.>, <max.>)
如果min.和max.相等,那么你可以只显示一个数字作为简写的格式。
注意,这个范围没有指定在交互的一次执行过程会发生多少次迭代。它只约朿能够发生多少次迭代(仍然由交互的有效执行决定)。为了指定任意次迭代都是有效的,你可以把范围设置为(0,*),其中星号意味着“没有上限”。事实上,如果没有在loop交互操作符的右侧指定任何范围,那么(0,*)就是默认的情况。
和通常一样,包含在loop组合片段中的操作数可以拥有一个守卫(显示在操作数顶部的方括号中)。在loop至少循环迭代了括号中指定的min.次之后,才会对守卫进行估值。一旦它拥有了值,loop就会继续,直到守卫估值为假,或者循环迭代了括号中指定的max.次数。
图7.15显示了图7.1中所示的大型序列图的一部分,只有一点区別:此处显示的loop组合片段指定了一个守卫。这个组合片段封装了三条消息的消息发送和消息接收事件,另外还封装了一个行为执行开始和行为执行结束事件。这个完整的事件序列在Execute Hohmann Transfer 和 Main Success Scenario交互的一次执行中可以发生任意多次(正如Ioop交互操作符后面的范围所传达的)。然而,当守卫sensorStatus ==“Ready”估值为假的时候,这个循环就会终止。
9.4、par操作符
带有par交互操作符的组合片段代表两个或多个系列的事件,它们会在交互的执行过程中并行进行。和alt组合片段一样,par组合片段有两个或多个操作数(区域),其中会包含那些并行的系列事件。
前面讲过,显示在生命线较高位置的事件,要在同一生命线较低位置的事件之前发生。然而,par组合片段会改变这一点。如果两个事件发生出现在par组合片段的不同操作数上,那么其顺序无法判断。更加正式的说法是,那两个事件在交互的执行过程中能够以任意的顺序发生,而得到的执行都会是有效的。然而,为了更清楚地表示,显示在par组合片段同一个操作数中的事件,还是会以一条生命线上从上到下的顺序发生。
你可以选抒为par组合片段的每个操作数指定一个守卫。如果那样做的话,它的意义就和之前一样:只有守卫的估值为真,那个操作数中的事件才会发生。尽管这么说,建模者很少会为par组合片段的操作数指定守卫。
图7.16显示了图7.1所示交互的变体,并且经过了很大程度上的简化。这个交互包含了一个par组合片段。在第一个操作数中,fc生命线上有两个事件。在第二个操作数中,fc生命线上有八个事件。par组合片段表示这两个系列的事件会彼此并行发生。
第一个操作数中的两个事件可能会在第二个操作数中的八个取件之前发生。第二个操作数中的八个事件也可能在第一个操作数中的两个事件之前发生。当然,这两个系列的事件也可能会以某种不确定的方式交错发生。这是由并发行为本身决定的,它们的相对顺序是无法提前决定的。
10、交互使用
在SysML活动图中,可以把高层次的活动分解为低层次的行为——通过调用行为动作触发的行为。类似地,你可以把高层次的交互分解为低层次的行为——通过叫做交互使用(interaction use)的元索触发的行为。
交互的标识法是一个矩形。它会出现住序列图的外框之内。矩形左上角出现的分隔框包含字符串ref,表示这个交互使用是对你在模型层次关系中某处定义的另一个交互的引用。那个被引用的交互的名称显示在矩形之中。矩形必须放在参与那个被引用交互的生命线上。(参与生命线会隐藏在矩形之后。)
建模者一般会因为下面两种原因向交互添加交互使用。
- 1)为了重构出事件的子集,这个子集对于几个高层次的交互是通用的,并把那个子集放在单个低层次交互的某个位置 。
- 2)为了把一个复杂的时间系列(在高层次交互中)分解为更可读的低层次交互序列 。
图 7.17 显示了最初显示在图 7.8 和 7.10 中的 Initialize MANS 和 Main Success Scenario交互。 然而,在这幅序列图中,我重构出了事件的子集,把它们放在名为 Bring MANS Hardware Online 的低层次交五中,并添加了一个交互使用,以显示被触发的低层次交互。
图7.18显示了 Bring MANS Hardware Online交互,它包含图7.17的交互重构出来的事件。引用的交互本身可以包含交互使用,可以让你创建任意深度的行为分解。
如果有消息进入或者离开交互使用(如图7.17所示),那么它引用的交互必须有相匹配的消息从外框进入或者输出到外框(如图7.18所示)。正式的情况下,SysML 会在实际门声明那个消息进入(或离开)交互使用。SysML会在正式门声明那个消息进入(或离开)引用的交互。
实际门和正式门没有专有标识法。它们隐藏在消息与交互使用或者图外框相交的地方。关键是:在实际门和正式门之间必须有一对一的对应关系。(只要你记着让消息匹配,即便不知道这个术语,也可以完成所需的任务 。)
11、小结
序列图表示系统行为随着时间推移的变化信息,关注系统中特定部分之间发生的通信 。 建模者通常会用序列图为测试案例建模一一通过用例的单一执行路径,并且带有指定的输入值和期望的输出值 。 序列图的重要优势在于,它可以完整、清楚地指定系统行为 。 它会传达所有三种重要的信息:行为发生的顺序、哪个结构执行了哪个行为、哪个结构触发了哪个行为 。 因此,序列图经常会作为系统生命周期开发阶段的输入项 。