第七章:序列图
序列图简介
序列图是另一种可以用来说明系统动态行为信息的SysML图。建模者可以使用叫做生命线的元素,为系统行为中的参与者建模,然后使用生命线之间的消息,为那些参与者之间的交互建模。建模者可以指定交互的时间约束和持续期间约束。建模者还可以使用各种交互操作符来指引交互的执行。使用交互可以对一系列交互之间的行为分别建模。
序列图是一种行为图;和活动图一样,它表示了系统的一种动态视图,这种视图会说明随着时间推移而发生的行为和事件的序列。交互:模块的各个部分会通过操作调用和异步信号彼此交互,以产生浮现式的行为。序列图是对行为的精确说明,因此它很适合用在详细设计方面,即作为开发的输入项。很多商业化建模工具模糊了设计和开发之间的界限;它们让建模者可以基于显示在序列图中的交互自动生成生产环境质量要求的源代码。这是可能实现的,因为序列图会表达自动转换成源代码所需的三种前提信息:行为执行的顺序、哪个结构会执行哪种行为,以及哪个结构会触发哪种行为。但是精确度和可读性不可兼得。随着行为中控制逻辑复杂度的增加,序列图很快就会变得不可读。因此,建模者通常会使用序列图作为图形化的测试案例说明书,只用它显示大型(更复杂)用例行为的单一场景。然而,我们不应受限于这种习惯性用法。序列图是一种意义非常丰富的媒介,可以针对一般目的的行为说明工具。因为序列图是一种万能的行为图,所以建模者可以通过创建它来说明系统层级关系中任意级別的行为,可以在系统生命周期的任意时间点创建序列图。
序列图元素
序列图的图类型缩写是sd。在序列图中唯一允许使用的模型元素就是交互。序列图的外框总是表示建模者在系统模型某处定义的一个交互。交互本身是一种模型元素;和活动一样,它是一种行为。和活动、模块和包一样,交互也是一种命名空间。因此,它可以在模型层级关系中包含一系列命名的元素(像生命线、事件发生和消息)。那些被包含的元素可以出现在相关序列图的外框之内。
生命线是代表交互参与者的一种元素,生命线代表交互中参与者的单一实例,它会与其他生命线交换数据。显示特定交互中的生命线和拥有该交互的模块的组成部分属性相关。那个模块可能是设计者们感兴趣的系统、一个子系统或者一个组件。生命线的标识法是一个矩形,附着有虚线,在序列图中显示为上下方向。虚线代表组成部分属性的生命(和交互中的参与者相关,不一定在操作的系统中一直存在)。时间会沿着生命线向下进行,先发生的事件会显示在生命线中比较高的位置,而后发生的会显示在比较低的位置。但是序列图只表达事件发生的时序。生命线上两个事件发生之间的距离没有任何意义;有意义的只是哪个更高一些,哪个更低一些。矩形指的是生命线的头部。它包含了一个名称字符串,可以标识生命线所代表的组成部分属性。名称字符串的格式如下:“<part property name>[<selector expression>]:<type>”。已命名的组成部分属性(part property name)的类型是一个模块或者执行者,建模者在模型层级关系中的某处已经对其进行了定义,常见的是模块。选择器表达式(selector expression)是名称字符串的可选部分。在显示的时候,它会出现在紧挨着生命线字符串后的方括号之中。选择器表达式会指定生命线代表的特定实例(确切地说是:选择器表达式指定(在一个集合中)生命线代表的特定实例)。只有在选择器表达式真正能够影响集合中的某个实例参与到交互中的时候,才需要在生命线上添加一个选择器表达式。在生命线上可以出现6种类型的事件:消息发送事件、消息接收事件、生命线创建事件、生命线销毁事件、行为执行开始事件、行为执行结束事件。
消息
消息代表的是发送生命线和接收生命线之间的通信。那种通信可能是启动行为、在行为的末尾发送回应、创建生命线或者销毁生命线。消息的标识法一般是带有箭头的线,连接发送和接收生命线。消息的尾端与发送生命线连接,消息的箭头端与接收生命线连接。每种类型的消息都有其特殊的线形(例如虚线或实线)和箭头形状(开口箭头或者实心箭头)。消息上面还会有一个指定消息名称的字符串,它会和其他可选的消息片段(参数名称、参数值和返回值)在一起。名称字符串的具体格式取决于消息的类型。一条生命线可以同时是消息的发送方和接收方。
在交互的生命线上可以有六种事件发生。其中两种是消息发送事件和消息接收事件,可以把这两种称为消息事件。每次建模者创建从一条生命线到另一条的消息时(或者从一条生命线到其自身),建模者就同时为消息发送事件和消息接收事件建模了。在消息的尾端与生命线相交的地方,就会存在消息发送事件;在消息的箭头端与生命线相交的地方,就存在消息接收事件。简而言之,消息事件没有任何标识法,它隐藏在消息末端和生命线相连的地方。
一般在交互中会出现四种类型的消息:异步消息、同步消息、回复消息和创建消息。(SysML还定义了两种其他消息类型(找到的消息和丢失的消息)但是在曰常实践中很少会用到它们。)每种类型的消息都有独特的标识法。在大型交互的情境中每种类型为不同的目的服务。
1)异步消息代表的是发送生命线和接收生命线之间的一种通信,而发送方会在发送消息之后马上继续执行。发送方不会等待接收方完成被触发的行为,也不会等待接收方在完成行为的时候发送回应。异步消息的标识法是带有开口箭头的实线(从发送生命线画向接收生命线),在线上有一个标签,格式如下:“<message name> (<input argument list> )”。消息名称(message name)必须与接收生命线所拥有的接收的名称匹配。输人参数列表(input argument list)是可选的信息片段。如果选择不显示它,那么就可以在消息名称之后显示一组空括号,或者什么都不显示。如果选择显示参数,那么就要以逗号分隔的列表形式显示,列表中的每个参数都会遵循下面的格式:“<input parameter name> = <value specification>”。输入参数名称(input parameter name)是可选的;如果显示的话,那么它必须与被触发的接收的输入参数名称匹配。所传递的值(value specification)必须与相关输入参数的类型相匹配(例如:Integer、Real、Boolean、String、Complex,或者自定义的值类型,或者在模型某处定义的模块)。
2)同步消息代表的是发送生命线和接收生命线之间的一种通信,其中发送方会等待接收方完成被触发行为的执行,发送回复消息,然后才会继续自身的执行。同步消息的标识法是带有实心箭头的实线(从发送生命线画向接收生命线)。同步消息的标签和异步消息的标签格式相同:“<message name> (<input argument list> )”。消息名称(message name)必须与接收生命线所拥有的接收的名称匹配。输人参数列表(input argument list)是可选的信息片段。格式和异步消息一样:“<input parameter name> = <value specification>”。当接收生命线执行与被触发的操作相关的行为时,所有在同步消息中传送过来的参数都是可以访问的。
3)回复消息代表一种标记同步调用行为结束的通信。它总是(通过交互中早期的同步消息)从执行行为的生命线发送到触发行为的生命线。回复消息的标识法是带有开口箭头的虚线。可以选择在回复消息上显示一个标签,格式如下:“<assignment target> =<message name>(<output argument list> ) : <value specification>”。消息名称(message name)必须与相应同步消息的名称匹配(那也是同步消息所触发的操作的名称)。冒号后面的值说明(value specification)代表刚刚完成执行的行为的返回值。但是,只有在被触发的操作真的拥有声明的返回类型时,这段字符串才会出现。赋值目标(assignment target)是可选的信息。如果显示的话,它表示捕获返回值的属性名称——触发操作的生命线拥有的属性(也就是接收回复消息的那个)。输出参数列表(output argument list)也是可选的。如果选择显示参数,那么它们就会以逗号分隔的列表形式显示,每个参数都会遵循下面的格式:“<output parameter name> : <value specification>”。输出参数的名称(output parameter name)是可选的,如果显示的话,它必须与被触发操作的输出参数名称匹配。建模者通常会忽略这条信息(output parameter name),只显示发送回调用者的值说明(value specification),作为被触发操作的输出。SysML还可以选择性地为列表中的每个输出参数显示赋值目标。(回复消息显示本身是可选的。)
4)创建消息代表在系统中创建新实例的通信——之后会参与到交互中的一个实例。创建消息的标识法是带有开口箭头的虚线。消息的尾端与发送生命线连接。消息的箭头端会和被创建的生命线的头部相连。生命线创建事件会存在于创建消息与生命线头部的交点处。(在那里也会存在消息接收事件,因此它和生命创建事件是同时发生的。)
析构事件代表生命线的结束,并在生命线所代表的系统中析构该实例。对于软件对象,析构一般指的是一种动作,它会释放分配给对象的内存。对硬件对象来说,析构可能指的是从系统中移除一个对象,或者销毁一件真实的物理对象。析构事件的标识法是X形状的叉,位于被销毁的生命线底部。X可能会显示在生命线的底部,不附加任何消息。这表示在交互的该时间点,生命线已经结束。X还可能会与消息的箭头端连接。这表示析构事件是生命线接收特定类型消息的结果,这个特定类型消息叫做删除消息。SysML(和UML)规范都很奇怪,没有说明删除消息所需要的线形以及箭头风格。语言只是规定删除消息必须在析构事件中结束。(析构事件是能够在生命线上出现的后一个事件。)
能够出现在生命线上的最后两种事件类型是行为执行开始事件和行为执行终止事件。行为执行开始事件一般隐藏在生命线接收同步或异步消息的地方。行为执行终止事件一般隐藏在生命线发送回复消息的地方。
执行说明:显式地表示行为在生命线的何处开始和结束,会消除不明确的情况。执行说明的标识法是一个狭窄、垂直的矩形——可能是空白的,也可能带有填充图案;在生命线执行一个行为的时候,它会在交互的一段时间内覆盖生命线。矩形的顶部会显式地标记行为执行开始事件。矩形的底部会显式地标记行为执行终止事件。行为执行开始事件通常会和消息接收事件同时发生。行为执行终止事件一般会和发送回复消息同时发生。有时候,一条生命线会在一个外部行为的情境中执行内嵌的行为。可以使用小型的执行说明来表达这一点,它会与同一条生命线上的更大的执行说明部分重叠。
约束
序列图可以指定各种类型的约束。约束是一个布尔表达式,一般显示在大括号{}中,并且能够出现在各种类型的SysML图中。在交互情境的日常实践中可以使用三种约束:时间约束、期间约束和状态常量。
1)时间约束会指定单个事件发生所需要的时间间隔。那个时间间隔可能是单独的时间值(也就是说.大值和小值都一样),也可能是持有一个时间值的属性。当交互在系统操作过程中执行的时候,只有那个事件发生在时间约束指定的时间间隔中,才能够认为它能有效地执行。
2)持续期间约束会指定两个事件发生所需的时间间隔。同样,这里的时间间隔可能是单独的时间值,也可能是持有吋间值的厲性。*交互在系统操作中执行的时候,只有那一对事件发生相隔的时间恰好落在期间约束所指定的时间间隔中,才能够认为它们是有效的。*可以把期间约束应用在消息发送事件和相关的消息接收事件上,但会限制消息的传输时间。当为此应用期间约束的时候,需要把它放在大括号{}之中,然后显示在约束消息的上面或下面。
3)状态常量是一个条件,建模者可以在特定的事件发生之前(紧挨着的上面)指定给特定的生命线。*在交互的有效执行中,那个条件在那个事件发生的时间点上必须为真。*可以使用状态标识法(圆角矩形)来说明状态常量,该标识法一般会出现在状态机图中。和之前一样,这个标识法会在特定事件发生之前出现在特定的生命线上。然而,如果使用这种形式,状态常量不会包含布尔表达式;它只会包含状态的名称,生命线在事件发生的那个时刻必须处于那种状态。然而,只有模块(它的名称显示在生命线头部的冒号之后)真正拥有一个状态机行为,并且该行为拥有定义好的一系列状态,这种标识法才有意义。
组合片段
组合片段是一种机制,让建模者可以向交互添加控制逻辑(像决定、循环、并发行为)。组合片段的标识法是一个矩形,它会出现在序列图外框内的某处。矩形会放在一个或多个生命线之上,并封装在那些生命线之间传递的一条或多条消息(由那个组合片段定义的控制逻辑所决定的消息)。交互操作符:在矩形左上角的分隔框中用来指定控制逻辑显现的字符串。SysML定义了11种交互操作符,有四种可能会在日常建模工作中用到,分别是:opt、alt、loop和par。操作数在组合片段中以区域的形式显示:区域由虚线分隔,那条线会水平穿过矩形。组合片段中的每个操作数(每个区域)包含一条或多条消息,它们的出现与否基于该组合片段定义的控制逻辑。
1)opt组合片段只会拥有一个操作数(区域),所以看不到水平穿过矩形的虚线。带有opt交互操作符(在左上角)的组合片段代表一系列可选的事件,如果条件(称为守卫,guard)的估值为真,那么就会在交互的执行过程中发生。在那个操作数中的事件要么发生,要么不发生(基于系统操作过程中守卫的估值)。守卫是一个布尔表达式,放在一对方括号之间,显示在opt组合片段的顶部附近。必须把守卫放在组合片段中第一个事件发生的生命线上。所有出现在布尔表达式中的属性都必须是那个生命线的属性,或者是总体上拥有交互的模块的属性。
2)带有alt交互操作符的组合片段代表两个或多个可替换的系列事件,它们会在交互的一次执行中发生。alt组合片段必须拥有两个或多个操作数(区域),其中包含那些可替换的系列事件,每个操作数都会由水平穿过矩形的虚线分隔。alt组合片段中的每个操作数都拥有自己的守卫。只有一个守卫的估值可以是真,在那个守卫下的操作数中的系列事件会成为执行的一部分;在所有其他操作数中的事件都会被完全跳过。建模者的职责是要确保alt组合片段中的系列守卫都是互斥的。可以为(最多)一个操作数使用预定义的守卫else。只有所有其他守卫都为假的时候,这个守卫才能为真。但是并不一定要使用else守卫。可能没有任何一个守卫为真(这种情况下,会跳过整个alt组合片段)。
3)带有loop交互操作符的组合片段代表一系列事件,它们可以在交互的一次执行过程中发生多次。和opt组合片段一样,loop组合片段也只有一个操作数(区域)。建模者可以指定loop的min.和max.迭代次数,把它放在紧挨着loop交互操作符右侧的括号中。这个范围会以下面这样的格式指定:“(<min.>,<max.>)”。如果min.和max.相等,那么可以只显示一个数字作为简写的格式。这个范围没有指定在交互的一次执行过程会发生多少次迭代。它只约朿能够发生多少次迭代(仍然由交互的有效执行决定)。为了指定任意次迭代都是有效的,可以把范围设置为(0,*),其中星号意味着“没有上限”。事实上,如果没有在loop交互操作符的右侧指定任何范围,那么(0,*)就是默认的情况。包含在loop组合片段中的操作数可以拥有一个守卫(显示在操作数顶部的方括号中)。在loop至少循环迭代了括号中指定的min.次之后,才会对守卫进行估值。一旦它拥有了值,loop就会继续,直到守卫估值为假,或者循环迭代了括号中指定的max.次数。
4)带有par交互操作符的组合片段代表两个或多个系列的事件,它们会在交互的执行过程中并行进行。和alt组合片段一样,par组合片段有两个或多个操作数(区域),其中会包含那些并行的系列事件。如果两个事件发生出现在par组合片段的不同操作数上,那么其顺序无法判断:那两个事件在交互的执行过程中能够以任意的顺序发生,而得到的执行都会是有效的。然而,为了更清楚地表示,显示在par组合片段同一个操作数中的事件,还是会以一条生命线上从上到下的顺序发生。可以选抒为par组合片段的每个操作数指定一个守卫。其意义就和之前一样:只有守卫的估值为真,那个操作数中的事件才会发生。一般而言建模者很少会为par组合片段的操作数指定守卫。
交互使用
**交互使用(interaction use)**的元素触发的行为:把高层次的交互分解为低层次的行为。交互的标识法是一个矩形,它会出现住序列图的外框之内。矩形左上角出现的分隔框包含字符串ref,表示这个交互使用是对建模者在模型层次关系中某处定义的另一个交互的引用。那个被引用的交互的名称显示在矩形之中。矩形必须放在参与那个被引用交互的生命线上。一般会为下面两种原因向交互添加交互使用:1)为了重构出事件的子集,这个子集对于几个高层次的交互是通用的,并把那个子集放在单个低层次交互的某个位置;2)为了把一个复杂的时间系列(在高层次交互中)分解为更可读的低层次交互序列。引用的交互本身可以包含交互使用,从而可以创建任意深度的行为分解。如果有消息进入或者离开交互使用,那么它引用的交互必须有相匹配的消息从外框进入或者输出到外框。正式的情况下,SysMIL会在实际门声明那个消息进入(或离开)交互使用,SysML会在正式门声明那个消息进入(或离开)引用的交互。实际门和正式门没有专有标识法;它们隐藏在消息与交互使用或者图外框相交的地方。关键是:在实际门和正式门之间必须有一对一的对应关系。
小结
序列图表示系统行为随着时间推移的变化信息(重点是:系统中特定部分之间发生的通信)。序列图的重要优势在于:序列图可以完整、清楚地指定系统行为。它会传达所有三种重要的信息:行为发生的顺序、哪个结构执行了哪个行为、哪个结构触发了哪个行为。因此,序列图经常会作为系统生命周期开发阶段的输入项。