课件
UML 状态图中的组合状态
在 UML 状态图中可以通过状态嵌套的方式来降低图表的复杂性。一个组合状态可以包含一或多个状态,组合状态可以实现在不同抽象层次上体现状态的细节。
比如定义“或”状态是指在组合状态内部的状态只能选其一,上图例子左图中,受雇状态分为试用期和正式期,一个员工要么处在试用期要么已经转正,只能二者选其一。一个“与”状态,用于表达的是并发状态,当进入组合状态时在每一个组合状态中选择一个子状态,也就是说“与”状态它的子状态进一步的嵌套了一个或状态的子状态。右边的这个是我们即同时拿到薪水也会被分配到某个项目组中完成工作。
组合状态的另一个名字就是超级状态(Superstates)。组合状态的好处是通过把一些状态封装到一个超级状态中,把一些反复重复发生的迁移放到组合状态去描述,从而降低了复杂度。
组合状态例子:
带有组合状态的状态机图,和不带组合状态的状态机图,其实在语义上是相等的,组合状态只是一种文法上的转换。
组合状态的状态迁移
迁移的起点和终点的不同,含义是不同的:
- 指向组合状态的边界的状态迁移,等价于指向该组合状态初态的一个迁移。也就是说属于该组合状态的入口条件将要被检查。
- 从组合状态边界转出的迁移,等价于从该组合状态的终态发出的迁移,也就是说要检查该出口条件以及执行出口动作。
迁移也可以透过组合状态的边界直接指向组合状态里头的子状态,这样就和普通的状态迁移是一样的,只是去执行迁移的线上的条件和动作。
例子:
现在要在图中增加一个新的状态和相关的状态迁移,表示在任何一个订单上的产品投递之前,都可随时取消订单。
参考答案:
为了降低模型复杂度,我们引入新的组合状态 Active 活动订单,并在这个组合状态的边界引出一条指向取消订单的迁移。
UML 状态图中的历史状态
在 UML 状态图中另外一个相对来说比较高级一点的概念就是历史状态(History State)。历史状态实际上是一个伪状态,当激活这个状态时,我们可以保存上一次从组合状态中退出时候所处的那个子状态,用 H 来表示。当再次进入该组合状态的时候,我们可以直接进入到这个状态,而不是再次从组合状态的初态开始执行。
H 状态也可以进行进一步的延伸定义成 H 状态。H 状态只记录一层组合状态的历史,就是上一次的历史,只能回退一次。而 H 状态可以记住任何深度的组合状态的历史,可以看成是一个历史状态的堆栈。
下图是历史状态定义的例子:
比如我们将历史状态定义到组合状态备份当中,如果上一次的备份如果进行了一半退出的话,下一次备份可以从上一次备份中断的地方继续开始。
例:课程注册
这是带有历史状态定义的课程注册过程的一个状态图。比如,一位同学在课程注册的过程中临时退出,那么下一次再进入该课程注册活动的时候,我们将从上一次拟选课终止的地方继续执行,也就是说整个选课过程首先要先选四门必选课,再选两门选修课,在这个过程中会保存以往的历史记录,在中断的地方重新开始执行,这种机制在允许挂起的状态机中是非常有用的。
状态图的工具支持
由于状态图是一个完全形式化的模型,可以用工具来支持根据状态图生成代码的工作。
比如上图,是一个对字符串进行扫描,提取字符串中双尖括号内部的字符串的一段逻辑,那么我们用 RationalRose 自动生成代码的话,就会获得这样的类的定义:
状态图与其他 UML 图的关系
- 在 UML 状态图中,出现的所有事件的定义应该和顺序图中该对象的输入消息一致
- 状态图中应针对类图中具有重要行为的类进行建模
- 每个事件、动作对应于相应类中的一个具体操作
- 状态图中每个输出消息对应于其他类的一个操作
- 状态图中的操作定义等价于类图中的操作定义
状态图建模风格
在状态图的建模过程中,要注意:
- 把初态放置在左上角,把终态放置在右下角,便于读者导航
- 状态定义的英文要用过去式来命名转移事件,这样做是为了反映状态的迁移是某个事件的结果,也就是说事件出现在迁移发生之前,因此引用这个事件的时候要用过去式,比如 Cancelled, Closed, Scheduled
- 警戒条件不要重叠,从一个状态出来的相似的转移,它的警戒条件彼此之间应该是互斥的,而且应该是尽量做到全覆盖
- 不要把警戒条件置于初始转移上,当警戒条件不满足的时候就不能够开始对象的行为
状态图的检查表
状态图建模完成之后,要进行检查,包括:
- 一致性检查:
- 状态图中所有的事件是否在类图中对本对象类方法的定义中出现
- 状态图中所有的动作是否在类图中其他对象类的定义中出现
- 绘图风格检查:
- 每个状态的命名是否意义明确并且唯一
- 是否对行为复杂的状态使用了组合状态
- 状态图中是否包括了太多的细节,是否容易阅读
- 定义警戒条件的时候是不是含义明确,会不会引入不确定性
下述情况不适宜使用状态图:
- 当大部分的状态转移为“当这个状态完成时”,这种情况应该用活动图来建模
- 有很多来自对象自身发出的触发事件,因为这是在描述对象自身的行为,它不是状态迁移的这种场景
- 状态代表的信息与类中的属性赋值并不一致