第七章状态图现在要开始学习的是如何随时间变化的UML动态元素。本章的主要内容是:●什么是状态图。●事件、动作和监视条件。●子状态:顺序的和并发的。●历史状态。●为什么状态图很重要。在前—章的最后曾提到,这一章要开始学习你在前面没有遇到过的另一类UML元素。这个新类被称为行为元素,它们能够展示UML模型部件如何随时间变化。事物的一个普遍的现象是随着时间的流逝,都要经历变化。任何计算机系统也是如此。当系统与用户(也可能是其他系统)交互的时候,组成系统的对象为了适应交互要经历必要的变化。如果要对系统建立模型,那么模型中必须要反映出这种变化。本章将学习这类元素中的一种:状态图。7.1什么是状态图一种表征系统变化的方法可以说成是对象改变了自己的状态(state)以响应事件和时间的流逝。下面是几个简单的例子:当你拉下电灯的开关时,电灯改变了它的状态,由关变为开。当你按下远程遥控器的调频按钮时,电视机的状态由显示一个频道的节目变为显示另一个频道的节目。经过一个适宜的时间后,洗衣机可以由洗涤变为漂洗状态。UML状态图能够展示这种变化。它描述了一个对象所处的可能状态以及状态之间的转移,并给出了状态变化序列的起点和终点。状态图也被引用为状态机(statemachine)。状态图与类图、对象图和用例图有着本质的不同。前面章节介绍过的这3种图能够对一个系统或者至少是一组类、对象或用例建立模型。而状态图只是对单个对象建立模型。通常状态名的首字母要大写,并且最好给状态一个以“ing”为结尾的名字。(例如“Dialing”、“Faxing”)。当然有时也无法起这样的名字(例如“Idle”)。7.1.1符号集下图显示了圆角矩形代表一个状态,状态间带箭头的实线代表状态的迁移(转移)。箭头指向目标状态。图中的实心圆代表状态转移的起点,公牛眼形圆圈代表终点。7.1.2在状态图标中增加细节UML提供了在状态图标中增加细节的选项。类似于类的图标可以被分成3个区域(名字,属性和操作区域),你可以把状态图标也分成3个区域。最上面的区域保存状态名(不管分不分区都得有状态名),中间区域保存状态变量,下面区域保存的是活动。下图说明了状态图标中的细节。状态变量,像计时器或者计数器—样,有时很有用途。活动是由事件和动作组成:3个常用的事件和动作是入口动作(entry),即系统进入该状态时要发生的动作;出口动作(exit),即系统离开该状态时要发生的动作;动作(do)是系统处于该状态时要发生的动作。还可以增加其他的动作或事件。传真机例子可以用来说明状态变量和活动。当它发传真时,换句话说就是当它处于Faxing(发传真)状态时,传真机记录下发送传真的日期和时间(用状态变量“Date”和“Time”来表示),并且记录接收者的电话号码和名字(用状态变量“PhoneNumber”和“Owner”来表示。在这个状态下,传真机参加给传真“增加日期戳(addingadatestamp)”和“时间戳(timestamp)”的活动,以及增加电话号码和接收者姓名到传真机中。这个状态下的其他活动是机器拉进传真页,逐页传真,完成传输任务。在Idle状态下,传真机要显示出当前的时间和日期。下图显示了传真机的状态图。7.1.3增加转移的细节:事件和动作可以对状态转移线添加一些细节。可以指明引起转移发生的事件(触发器事件)和引起状态变化所需执行的计算(动作)。添加的事件和动作写在转移线上,触发器事件和动作名之间用反斜杠隔开。有时一个事件会引起没有相关动作的状态转移,或者有时一个转移是由于某个状态完成了它的活动所引起(而不是由于事件引起)。这种类型的状态转移被称为无触发器转移。图形用户界面(GUI)是一个可以说明状态转移细节的例子。在这里,假设GUI可以处于以下3种状态之一:Initializing(初始化)。Working(工作)。ShutDown(关闭)。当打开PC电源的时候,自启动发生。因此TurningthePCon(打开PC)是一个触发器事件,它导致了GUI的状态转移到Initializing状态,而Bootup(自启动)是一个在转移过程中执行的动作。由于Initializing状态中活动的完成,GUI将转移进入Working状态。当你对PC选择ShutDown(关闭机器)时,就生成了一个引起转移到ShutingDown状态的触发器事件,最后PC自己切断电源,整个过程结束。下面的状态图捕获了GUI的这些状态和转移。7.1.4增加转移的细节:监视条件上面对GUI的状态变迁还有考虑不全之处。首先,如果你离开,你的计算机将无人照管或者你漫无目的坐在一旁,不打字或不碰鼠标,那么过一段时间屏幕保护程序就会运行。用状态转移的术语来说,就是如果GUI在足够的时间内没有接收到用户的输入,那么它将从Working状态转移到另一种状态——Screensaving(屏幕保护)状态。进入屏幕保护状态取决于指定的时间间隔。比如是15分钟。15分钟的时间间隔是一个保护条件——当满足这个条件时,转移才能发生。下图是GUI加入了Screensaving状态和保护条件的状态图,注意图中的保护条件[isTimeout],被写成一个布尔表达式。7.2子状态我们建立的GUI状态模型好像仍然少了什么。特别是working状态,应该比以上状态图所表示的内容更为丰富才对。当GUI处于working状态,幕后同时进行着许多事情,尽管这些事情并未在屏幕中显现出来。GUI始终在等用户的动作——敲键盘、移动鼠标或者按下鼠标按钮。然后它必须注册这些输入然后改变屏幕显示来反映用户的动作——例如,如果你移动鼠标则屏幕就移动光标;如果你按下键盘上的“a”键,屏幕上就显示出字符“a”。因此GUI处于working状态时仍然要经历变化,即状态的变化。因为这些状态存在于单个状态之中,因此它们被称为子状态。子状态以两种形式出现:顺序子状态和并发子状态。7.2.1顺序子状态正如名字所暗示的那样,顺序子状态按照顺序一个接着一个出现。重新分析前面提到的GUI的Working状态,可以得到以下的状态序列:AwaitingUserInput。RegistringUserInput。VisualizingUserInput。用户输入触发了从Awaiting状态到Registering状态的转移。Registering状态内的活动引起了GUI到Visualizing状态的转移。在第3个子状态之后,GUI重新回到Awaiting状态。下图说明了在Working状态中的顺序子状态。7.2.2并发子状态在处于Working状态时,GUI并不是仅仅只等待用户的输入。它还要监视系统的时钟(Watchsystemclock)或者定期更新应用程序的界面显示。例如,一个应用程序可能包括一个屏幕时钟,它的GUI需要定期被更新。所有这些与前面的顺序子状态的转移同时进行。尽管每个状态序列是一组顺序子状态,但是两个状态序列之间是并发关系。并发状态之间用虚线隔开,表示状态序列之间是并发关系,如下图所示。前面类图中说了,当每个部分体只能属于一个整体时,这种关系叫组成关系。Working状态和它的两个并发部分之间也有类似的关系。因此,Working状态被称为组成状态。只包含顺序子状态的状态也是组成状态。7.3历史状态当屏幕保护程序正在运行时移动了鼠标的话,系统又会回到Working状态。这时将发生什么呢?屏幕显示又会回到GUI刚刚初始化时的状态吗?不是,而是回到屏幕保护程序运行之前的状态。状态图中能够表达出这种思想。UML提供了一个符号,这个符号能够用来表示当对象转移出该组成状态后,该组成状态能够记住它的活动子状态。这个符号是一个小圆圈中字母“H”,并用一条实线连接到被记忆的子状态。下图说明了working状态中的表示法。在状态图中,还未涉及由一个窗口所打开的其它窗口,换句话说,也就是子状态中嵌入的其他子状态。当一个历史状态记忆了各个嵌套层次的子状态时,这个历史状态就是深的。如果它只记亿了最高层次嵌入的子状态,那么就说这个历史状态是浅的。深的历史状态用圆圈中的“H*”来表示。历史状态、初始状态(用实心黑色圆圈代表)和终止状态(用牛眼形的圆圈代表)都是伪状态,它们没有状态变量和活动,因此是不“完整’的状态。在前面的例子中,引起从Screensaving转移到working的触发器事件可能是一个击键操作、一次鼠标移动或者一次鼠标点击。任何这种类型的事件实际上是一个从用户到GUI的消息。因为对象之间正是通过相互发送消息进行通信,因此这是一个重要概念。在这种情况下,触发器事件是从一个对象(用户)7.4消息和信号到另一个对象(GUI)的消息。在接收对象的状态图中,能够触发一个状态转移的消息叫做信号(signal)。在面向对象领域里,发送一个信号就等同于创建一个信号类的实例并将这个信号实例传送给接收对象。信号也有自己的属性。如果将信号看成是一个类,那么可以建立信号之间继承层次的类图。用来操纵电视机的遥控器是信号发送者的一个好例子,并且提供给我们建立信号类层次模型的机会。这个例子也提供了学习使用构造型的好机会。记住构造型是UML的自扩展方式。UML没有专门为信号类提供图标,因此我们可以通过在每个类图标中都包含《Signal》的扩展图来表示信号类。下图显示了一种遥控器的信号层次。你还可以使用《Signal》类图标的方式对引起对象状态转移的事件做详细说明。7.5为什么状态图很重要状态图能帮助分析员、设计员和开发人员理解系统中对象的行为。类图和对应的对象图只展示出系统的静态方面。它们展示的是系统静态层次和关联,并能告诉你系统的行为是什么。但它们不能说明这些行为的动态细节。开发人员尤其要知道对象是如何表现自己的行为的,因为他们要用软件实施这些行为。仅仅实施对象是不够的,开发人员还必须让对象做该做的事情。状态图可以确保开发人员能够清楚的了解对象应该做什么,而不用自己去猜测它。如果有了一幅展示对象行为的清晰图景,那么开发小组构造出的系统满足需求的可能性就会大大增加。7.7小结系统中的对象改变自身的状态以响应事件和时间流逝。UML状态图就能捕获这些状态变化。状态图的焦点是一个对象的状态变化。状态用一个圆角矩形表示,状态转移用带箭头的实线表示,它指向目标状态。状态图标中要写明状态名,并且可以包括状态变量和活动的列表。转移可能作为对触发事件的响应而发生的,并且需要一个活动。转移也可能因为状态中的活动的完成而引起,这种方式发生的转移叫做无触发器转移。最后,转移还可能起因于一个特定条件(监视条件)的满足而引起。有时候状态可以包含子状态。子状态可能是顺序的(—个接着—个地发生)或者是并发的(同时发生)。包含子状态的状态被称为组成状态。历史状态是说明一个组成状态在对象转移出该组成状态之后还能够记住的子状态。历史状态可能是浅的也可能是深的。这个术语和嵌套的子状态有关。浅的历史状态只记忆了最顶层的子状态。而深的历史状态能够记忆所有层次的子状态。当一个对象向另一个对象发送稍息时,就触发了第2个对象的状态图中的某个转移,这个消息就被称为信号。使用扩展的构造型《Signal》的类图标,可以建立信号的继承层次。UML必须包括状态图,因为它能帮助分析员、设计员和开发人员理解系统中各个对象的行为。开发人员尤其需要知道对象是如何体现各自的行为的,因为他们要用软件实施这些的行为。只实施对象的静态特征是不够的,开发人员必须要让对象动起来做一些事情。建立状态图的方法和建立类图或用例模型类似。在建立类图时,要列出所有的类然后找出类之间的关联。在建立状态图时,首先要列出对象的状态,然后将注意力集中在状态之间的转移上。当研究每个转移时,要估计出是否需要触发器事件或者执行某些动作。布置状态图时,要使状态和状态之间的转移的交叉尽量的少。状态图的一个目标是图的清晰性。如果所绘制的图无人能够理解,那么就没法使用它。作业:1.给出下列术语的定义:转移、事件和动作。2.我们学习了GUI工作状态中的并发子状态。为屏幕保护状态也绘制一幅含有并发子状态的状态图。