第10章 状态图

上传人:痛*** 文档编号:182591185 上传时间:2023-01-26 格式:DOC 页数:25 大小:236KB
返回 下载 相关 举报
第10章 状态图_第1页
第1页 / 共25页
第10章 状态图_第2页
第2页 / 共25页
第10章 状态图_第3页
第3页 / 共25页
点击查看更多>>
资源描述
第10章 状态图在一个交互中,可能发送给单个对象一个或多个消息,并且,这些消息以特定的顺序被接收。但是,在另外的交互中,同一个对象可能接收完全不同的消息。根据各个交互的详细情况,特定消息发送到对象的顺序也可能根据情况的不同而改变。通过考虑对象能够参与的所有可能的交互,我们可以看到,在一个对象的整个生存期中,它必须能够合理地响应次序变动范围相当大的消息。在第8章,我们已经看到,对象图不是用来详细说明系统所有可能的状态的。首先,的确存在着太多的状态,不能用文档穷举;其次,除了要知道可能的状态是什么,我们还需要知道哪些状态是不可能,或者不合法的。出于完全相同的原因,顺序图和协作图也不是用来描述对象能够参与的所有可能交互的视图。对这两种情况,解决方案是相同的,即使用表示法的更抽象的形式详细说明系统,而不是举例说明。在UML中,对象的行为规格说明是通过为对象定义状态机来给出的。状态机说明了对象对它在生存期期间可能检测到的事件的响应。在UML中,状态机通常是用一种称为状态图的图来文档化的。交互图和状态机给出的是系统动态行为的两个互补的视图。交互图显示了在较短的一段时间在系统中的对象之间传递的消息,通常是在单个用户产生的事务期间,因此这些图必需描述很多对象,即特定事务中所涉及的那些对象。另一方面,状态图自始至终在一个单个对象的整个生存期中跟踪该对象,指定该对象能够接收的所有可能的消息序列,以及它对这些消息的响应。10.1 依赖状态的行为许多对象展现出了依赖状态的行为。不严密地说,这意味着对象在不同时间将对相同的刺激做出不同的响应。例如,考虑一个简单的CD播放机的行为,播放机包括一个装CD的抽屉,如果当前有播放的CD,就放在抽屉里。还包括一个界面,界面包含三个按钮,标明“装入(load)”、“播放(play)”和“停止(stop)”。如果当前抽屉关着,装入按钮使之打开,如果是打开的,则使之关闭。停止按钮使播放机停止正在进行的播放。如果没有正在播放的CD时,按下停止按钮不起作用。最后,播放按钮播放抽屉中的CD,如果按下播放按钮时抽屉是打开的,则先关闭抽屉后再开始播放。这个CD播放机至少在两个方面表现出了依赖状态的行为。例如,如果抽屉开着,按下“装入”按钮将关闭抽屉,而抽屉关着的时候,按下“装入”按钮将打开抽屉。另外,如果正在播放CD,按下停止按钮就停止播放,但是如果没有播放CD,按这个按钮没有作用。在这个例子中,我们可以标识CD播放机能够处于的至少三个不同状态。按下“装入”按钮引起的不同结果表明我们需要区别“打开(open)”和“关闭(closed)”状态,而按下“停止”按钮的不同结果表明存在第三个状态,可能标记为“正在播放(playing)”,它不同于上面任一个状态。同样值得注意的是,CD播放机可以响应事件而改变状态。例如,重复地按下“装入”按钮将引起CD播放机在打开和关闭状态之间转换。这个例子中的三个状态符合CD播放机的实际状态中可观察到的差异确实令人愉快,但是情况并不总是如此。区分状态的基本原则是,处于一个特定状态的对象,对至少一个事件的响应和它处于其他状态时对该事件的响应不同。因而识别的状态可能对应于容易发现的对象的外部特征,也可能并不与之对应。用于行为建模的状态的概念应该区别于第2章所讨论的状态,在第2章,对象的状态被定义为在给定时间其属性的值的整体。状态的行为概念比这个更广泛:在两个时间一个对象的属性很可能不同,可是却处在相同的行为状态。对此,CD播放机的“关闭”状态可以提供一个例子:抽屉中有或者没有CD可以被认为是CD播放机不同的属性值,但是在任一情况下我们都可以认为播放机处于关闭状态。行为状态的识别并不是一个严格的过程。状态的不同,是通过处于不同状态的对象对事件的响应不同来区分的,但是什么看作是不同的响应,在某种程度上却是一个需要判断的问题。行为状态的重要特性是,第一,一个对象有若干个可能的状态,并且在任何给定时间恰好处于这些状态中的一个。第二,对象可以改变状态,通常,它在给定时间所处的状态会由它的历史决定。最后,在不同时间,一个对象可能依赖其状态对同一刺激做出不同的响应。10.2 状态、事件和转换状态图(statechart diagram,通常简称为statechart),显示一个对象可能的状态,它能够检测到的事件,以及它对这些事件的响应。因此,为了构造一个对象的状态图,我们必须首先至少暂时地确立对象能够处于什么状态以及它能够检测什么事件。比如CD播放机的例子,我们已经标识了打开、关闭和正在播放状态,这将作为开发状态图的基础。用软件的术语,经常假定,对象检测到的事件就是发送给它的消息。然而,在刚开始设计时不必要这么具体:需要的只是对象能够检测到的外部事件这个更一般的概念。在CD播放机的例子中,能够检测到的外部事件只是按下三个按钮。因此,CD播放机的状态机将包括至少三个事件:“装入(load)”、“播放(play)”和“停止(stop)”。一般而言,检测到一个事件可能导致对象从一个状态移动到另一状态,这样的移动称为转换。例如,如果CD播放机处于打开状态,按下装入按钮将引起抽屉关闭,并且CD播放机移动到关闭状态。状态图上显示的基本信息是实体的可能状态以及它们之间的转换,或换句话说,检测各种事件的路径引起系统从一个状态转换到另一个状态。描述CD播放机的基本模型的状态图如图10.1所示。系统的状态以圆角矩形表示,其中写着状态的名字。状态转换用连接两个状态的箭头表示。每个这样的箭头必须标注一个事件的名字。这种箭头的含意是如果系统在处于箭头尾的时候接收到该事件,它将转到箭头的头所指向的状态。因此,事件通常将在状态图中出现多次,该对象可能在多个不同的状态检测到同样的事件。 图 10.1 CD播放机的一个简单状态机在图10.1中,从每个状态都有三个出发的箭头,每一个事件都可以被CD播放机检测到。这种完备性不是状态机的基本性质,而只是反映了CD播放机的用户在任何时候都可以按下三个按钮中的任何一个的事实。如果事件没有引起状态的改变,那么相应的转换只是在一个状态上形成回路。这种情况的例子是,在CD播放机已经处于正在播放状态,检测到播放事件时发生的转换。这样的转换被称为自转换(self-transition)。事件如同消息一样,能够带有数据,写在消息名字后面的括号中。播放机的例子中没有需要携带附加数据的事件,但在10.10节考虑的例子中将看到这样的例子。状态机的执行一个简单的状态机,如图10.1中所示的状态图,可以认为是按照下面的方式“执行”。在任何给定时刻,对象恰好处于图中所示的状态之一。这个状态称为激活状态(active state)。任何从激活状态出发的转换都是一个候选激发。例如,如果CD播放机的激活状态是“打开”状态,那么候选激发的转换有该状态上的自转换、标记着“装入”的到关闭状态的转换,以及标记着“播放”的到达正在播放状态的转换。能够引起转换激发的事件称为触发器(trigger)。当检测到一个事件时,该事件将激发从激活状态出发的标注着该事件名字的转换。这个激发的转换的另一端的状态就成为激活状态,这个过程可以再次重演,不同的是现在的候选激发将是从新激活状态出发的转换。从当前状态出发的事件如果没有标注为所检测到事件名字的,就忽略该事件,不激发任何转换,当前状态仍是激活状态。如果有必要指定在一个状态是激活的情况下检测到一个特定事件是错误的时候,可以定义一个错误状态,并增加一个到错误状态的转换并用被禁止的事件的名字加以标注。10.3 初始状态和终止状态图10.1中的图描述了CD播放机在使用时的运作机能,但是没有说明机器在开关时发生什么。我们将假定关机器时它不表现出任何行为,当开机时它总是直接到关闭状态。我们可以通过向状态图中加入初始状态表示后一种行为;初始状态用黑色的小圆点表示。从初始状态出发的转换表示创建或初始化对象时进入的状态。CD播放机的初始状态如图10.2所示,初始状态上的转换表示播放机在开机后总是处于关闭状态。注意从初始状态出发的转换上不应该写任何事件。 图 10.2 初始状态和终止状态除了初始状态,状态图还可以表示终止状态。终止状态代表对象在响应撤销、关掉,或其他终止事件时到达的状态,终止状态用大圆圈中加一个小圆点表示。一般而言,可以从许多不同的状态到达终止状态。在CD播放机的例子中,引起达到终止状态的事件是关播放机。我们可以用一个称为“关机(off)”的新事件对此建模。可以在任何时刻关掉播放机,因此用标注为“关机”事件的转换将终止状态连接到所有其他状态。终止状态的含意依赖于状态图所描述的对象的特性。如果一个软件对象,即类的实例,到达它的终止状态,那么它将完全被销毁:如果它有析构函数,那么将调用析构函数,并回收对象占用的内存。然而,图10.2显然不应该被解释说CD播放机在每次关机时都被实际地销毁:这样的设计不可能制造出来投入市场。实际模拟的是控制CD播放机的软件的行为:当关掉播放机时终止控制程序,并且机器将不响应任何事件,直到再次开机。10.4 监护条件图10.2中的状态图对CD播放机的行为给出了一个过分简单的描述。有一个问题是,当按下播放按钮时,播放机并不总是进入正在播放状态,而是当检测到该事件时如果抽屉中有CD才进入正在播放状态,否则如果抽屉还没有关闭就只是关闭抽屉并且进入关闭状态。这意味着在准确的模型中,关闭和打开两个状态都应该包含始于它们的两个标注为“播放”的转换。在任何给定时刻,实际上沿哪个转换前进将取决于在该时间抽屉中的内容。 图 10.3 按下“播放”时的两种可能后果图10.3显示了在CD播放机抽屉关着的时候按下“播放”按钮的两种可能的后果。这是一个非确定状态图的例子。这个图表明播放事件可以触发两个可能的转换,但并没有说明何时将激发其中的一个转换而不是另一个。原则上,不确定的图没有任何错误,但是如果建模的系统事实上是确定的,那么不确定的图必定是遗漏了系统的某些信息。在CD播放机的例子中,当按钮按下时不存在真正的非确定性,因为播放机接下来的行为由抽屉中的内容确定。更准确的模型应该表明是什么导致沿着一个转换而不是另一个转换前进,以消除图10.3中呈现的非确定性。在状态图上,这样的信息可以通过为播放转换增加监护条件来表示,表明在什么情况下将激发该转换。监护条件是转换的规格说明的一部分,写在标注该转换的事件名字之后,并用方括号括起来。监护条件通常以非正式的英语写出,如这里一样,但是如果要求,可以用更形式化的符号,如第12章描述的OCL语言写出。图10.4所示的CD播放机的扩充状态图包括了监护条件,区分了抽屉的非空和空的状态。为了简单起见,与当前讨论无关的初始状态和终止状态在这个图中省略了。 图10.4 使用监护条件区分转换监护条件对状态机执行的影响如下。当检测到一个事件时,将对标注着该事件名字的转换上的监护条件求值。如果转换有监护条件,那么只有在求值为真时这个转换才会激发。如果所有监护条件都是假值,并且没有无监护的转换,就忽略该事件。如果多个转换有值为真的监护条件,那么它们中只能有一个被激发。在这种情况下,非确定性再次引入到状态机,通常,要对一组离开转换上的监护条件进行挑选,以使得在任何给定时间,为真的不能超过一个。例如,假定CD播放机正处于打开状态时按下了播放按钮。发生的第一件事情是关闭抽屉;这是必要的,以便机器能够检测是否有碟片。重要的是要注意在这个时候,尽管事实上抽屉是关闭的,但CD播放机并不是在关闭状态。仍然是在打开状态下,评估播放转换上的监护条件的值,看应该激发哪个转换。这阐明了前面提出的一点,CD播放机的状态机中的状态不必要和CD播放机的实际状态恰好对应。如果有CD,将激发从打开状态到正在播放状态的转换。状态机直接从打开转到正在播放状态,并且不经过关闭状态,即使是暂时地。如果经过,那么它必须检测到第二个事件以激发它到正在播放状态的转换。然而,只有单独一个事件,即按下播放按钮事件,是将它从打开状态转换到正在播放状态所必需的。如果需要,抽屉关闭的物理事实在状态图上可以作为动作建模,如下节所描述的。10.5 动作状态图能够说明对象响应检测到的特定事件时做些什么。这通过在图中的相关转换上增加动作来表明。动作写在事件名字之后,前面加斜线。图10.5所示的是CD播放机的状态图,加入了动作表示抽屉实际打开和关闭的时间。动作可以用英语以伪代码的方式描述,也可以使用目标编程语言的符号。转换经常既带有条件,还带有动作。如果是这样,条件紧跟在事件名字之后,动作写在条件的后面。动作被看作是简短的、自包含的一段处理,所花费的完成时间可以忽略。动作的定义特征是它在转换到达新状态之前完成。这隐含着动作不能由对象可能检测到的任何其他事件中断,而必须总是执行完成。在这个意义上,不是原子的动作,或者对象处于给定状态时执行的处理,可以通过活动而不是动作描述,如第10.6节所描述的。 图 10.5 操纵CD播放机抽屉的动作入口和出口动作假定每次抽屉中有CD时如果按下播放按钮,CD播放机的播放头都将自己定位到当前曲目的开始。这可以在状态图上表示,方法是在每个标注“播放”的到达正在播放状态的转换上写一个适当的动作。但是,这相当不妥而且累赘,可以用更节省的方法达到相同的效果,即在正在播放状态中包含一个入口动作,如图10.6所示。每当一个状态变为激活状态时,紧接在通向该状态的转换上的动作完成之后就执行入口动作。例如,如果CD播放机处于打开状态,这时按下播放按钮,会关闭抽屉并激发到正在播放状态的转换。结果正在播放状态变成激活的,正在播放状态中的入口动作会立即被执行。状态还可以有出口动作,只要离开该状态的转换激发时就会执行。图10.6中的出口动作表示只要执行了引起停止CD播放的动作,首先发生的事情就是提起CD播放机的播放头。 图 10.6 入口和出口动作注意,自转换被看作状态改变。当一个状态上的自转换激发时,这个状态暂时不再是激活的,然后被再激活。这意味着,当沿着自转换前进时,如果该状态存在入口动作和出口动作,则首先执行出口动作,接着执行入口动作。在图10.6中,这意味着当CD正在播放时按下播放按钮的结果是播放头回到当前曲目的开头,从而重新开始这个曲目。实际上很多CD播放机都展现出了这种行为。10.6 活动显然,当处于正在播放状态时,CD播放机正在做某些事情,即播放CD的当前曲目。要花费时间完成的延续的操作可以表示为状态中的活动(activity)。和动作一样,活动也写在状态之中,前面加上“do”标记,如图10.7所示。 图 10.7 播放曲目的活动动作和活动之间的区别是这样的,动作被认为是瞬时的,活动不同,是发生在一段延续的时期之内。当状态成为激活状态时,它的入口动作被执行,然后开始它的活动,并且在状态处于激活的整个期间该活动都持续运行。在对象能够响应任何事件之前,必须完成入口动作。然而,活动可以被任何引起激发离开包含该活动的状态的转换的事件所中断。例如,在曲目结束之前如果检测到“停止”事件,那么播放曲目的活动会被中断并停止。当离开一个状态的转换激发时,在执行出口动作之前,活动的执行被中断。10.6.1 完成转换除了被事件中断,一些活动会自动地结束。例如,对图10.7中的正在播放状态中的活动,如果到了曲目结束时就会出现这种情况。在某些情况下,活动终止会引起状态转换,状态图应该指定接下来哪个状态成为激活状态。 图 10.8 完成转换可以利用完成转换(completion transition)做到这点。完成转换是没有事件标注的转换。在状态的内部活动正常终止,没有被外部事件中断时,完成转换可以触发。图10.8显示了CD播放机具有两个完成转换的正在播放状态,一个转换从正在播放状态通向关闭状态,另一个是正在播放状态上的自转换。当CD播放机正在播放时,用户可以按下播放或停止按钮中断当前曲目,如果这两个事件都没有被检测到,当前曲目最后会结束。在这种情况下,没有检测到外部事件,所以仅有完成转换是激发的候选转换。下来发生什么将取决于刚刚结束的曲目是否是CD的最后一个曲目。完成转换带有监护条件以区分这两种情况。如果刚刚播放完了最后一个曲目,到关闭状态的转换将激发,CD播放机将完全停止播放。否则,将激发自转换:曲目计数器递增,再次进入正在播放状态,CD播放机将开始播放CD上的下一个曲目。10.6.2 内部转换如上所述,自转换被认为是状态改变,所以如果图10.8中的任一个自转换激发,正在播放状态中的活动将终止,并且在再次进入该状态之前将执行状态的出口动作,然后执行入口动作,重新开始状态的活动。有时,需要对让对象停在同一状态,但是不触发状态的改变以及入口与出口动作的执行的这样的事件建模。例如,假定CD播放机有一个“信息(info)”按钮,当按下时显示当前曲目剩余的时间,其发生应该不中断正在进行的曲目播放。这可以作为正在播放状态中的内部转换来建模。内部转换写在状态之中,标注为引起该转换的事件的名字,如图10.9所示。和自转换不同,内部转换不会引起状态的改变,因此也不会触发入口和出口动作。 图 10.9 有内部转换的状态10.7 组合状态图10.5相当混乱和难以理解,而且其中还存在着一些冗余,某些转换以实质上相同的形式出现了不止一次。如果状态图要在实际中可用于复杂系统,就需要某些简化图的方法。通过允许一个状态包含若干子状态,提供了一种这样的技术。这些子状态,因为它们共享了某些特性,这些特性可以更简明地表示为单独一个“超状态”的特性,而将这些子状态组合成一组放入一个状态中。状态可以和其他状态共享的一个性质是它们的行为,或换句话说是它们参与的转换。例如,当CD播放机处于打开或关闭状态时,如果抽屉中有CD,它对播放事件的响应是一样的,即转到正在播放状态,并播放CD。稍不明显的是,即使没有CD的时候,响应也是相同的:播放机结束于关闭状态。这可能涉及状态的改变,也可能不涉及,取决于抽屉原先是打开的或关闭的,但是事件的实际结果是相同的。图10.10所示的CD播放机的状态图用超状态析出了这个公共行为。图中引入了一个称为“未播放(not playing)”的新状态,而打开和关闭状态现在显示为这个新状态的子状态。“未播放”状态通常称为是由这两个嵌套的子状态组成的组合状态(composite state)。这个新状态的存在只是为了将CD播放机的相关状态分组在一起,并没有引入任何新行为的可能性。组合状态具有下面的特性。第一,如果组合状态是激活的,那么它的子状态中只有一个必须也是激活的。所以在图10.10中,如果CD播放机没有播放,那么它必须处于打开或关闭两个状态中的一个。第二,在对象处于组合状态时检测到的事件可以触发从组合状态本身出发的转换,或者从当前激活的它的子状态出发的转换。例如,假设CD播放机处于关闭状态。如果检测到一个装入事件,将激发通向打开状态的转换,而打开状态将成为激活的。然而,这是未播放状态的一个内部转换,所以它仍然是有效的,只不过有一个不同的激活子状态。 图 10.10 使用子状态的CD播放机但是假如检测到的是播放事件。不存在从关闭状态出发的标注为“播放”的转换,但是有从未播放状态出发的这样的转换。因为这个未播放状态也是激活的,所以这些转换将被激活,并且根据抽屉中是否有CD,它们之中的一个或另一个将被激发。如果有CD,正在播放状态将变成激活状态。如果没有CD,关闭状态将变成激活的,不过是通过从未播放状态出发的自转换。子状态完全是正常的状态,并且到达它们的转换能够自由地穿过超状态的边界。图10.10中的从正在播放状态出发的停止和装入转换说明了这个特性。它们穿过了“未播放”状态的边界,但是在形式上和含意上对图10.5都没有改变。转换也可以连接一个超状态中的若干子状态,如打开状态和关闭状态之间的装入转换所示例的一样。最后,转换也可以从子状态到达超状态之外的一个状态,尽管图10.10中没有包含相应的例子。组合状态的特性组合状态中的嵌套状态形成了一种“子状态图”,并且,除了普通的状态,组合状态还可以包含初始状态和终止状态。组合状态中的初始状态表示如果到达组合状态的转换终止于组合状态的边界时,该默认子状态即成为激活状态。组合状态中的终止状态表明状态中正在进行的活动已经完成。到达终止状态使得从组合状态出发的完成转换能够激发。组合状态也可以有自己的入口和出口动作。这些状态被激活的方式与简单状态每当状态变成激活的或不再是激活的时方式完全相同。例如,假设按下CD播放机上的暂停按钮会引起播放被中断。当再次按下这个按钮时,从暂停的位置开始继续播放,也就是说,和按下播放按钮的情况不同,曲目不用重新开始。图10.11中的状态图模拟了这种行为。 图 10.11 暂停按钮的建模通过下面一些详细的事件序列可以更好地理解这个图。假如CD播放机处于未播放状态而且抽屉中有CD的时候,按下了播放按钮,那么标注为“播放”的转换将激发,使标记为“忙碌(busy)”的状态成为激活状态。因而执行该状态的入口动作,定位到当前曲目的开头。但是,这个转换没有指定忙碌状态的哪个子状态变成激活的,所以从初始状态到正在播放状态的转换激发,使正在播放状态成为激活状态。因此,开始播放当前曲目的活动。如果用户没有做任何事情打断这个过程,在曲目结束时,忙碌状态中从正在播放状态到终止状态的完成转换将激发,这是状态的活动终止时的正常行为。接着触发从组合状态出发的一个完成转换。假如还有另外的曲目要播放,将激发忙碌状态上的自转换,曲目计数器递增,并再次进入忙碌状态。如前所述,这将引起定位到新曲目的开头并开始播放。现在,假如用户在曲目结束之前按下了暂停按钮,这将中断播放曲目的活动,并引起到达“暂停(paused)”状态的转换激发。当用户再次按下暂停按钮时,回到正在播放状态的转换激发,并重新开始播放该曲目的活动。然而,在这种情况下,所有的转换都是忙碌状态内部的,所以不会触发定位到曲目开头的入口动作。因此,播放头不会移动,播放是从中断的那点重新开始,如所需要的那样。10.8 历史状态假如CD播放机的行为如同图10.11所描述的,并且用户在CD播放机处于暂停状态时按下了播放按钮,那么这将激发忙碌状态上的标记为“播放”的自转换,因而将退出暂停状态,再次进入忙碌状态。入口动作将导致找到曲目的开头,因为自转换只是通向组合状态的,因此沿着从初始状态出发的转换前进,使机器停留在正在播放状态,播放CD。但是,假如CD播放机展现出的实际行为不是这样的,而是在CD播放机暂停时按下播放按钮重新开始该曲目,但播放机仍然处于暂停状态。那么,重新开始播放之前,用户必须再次按下暂停按钮。对此建模的一种方法是将忙碌状态上标注为“播放”的自转换用两个自转换代替,一个标注在正在播放状态上,一个标注在暂停状态上。然而,如果到组合状态的转换能够“记住”上次组合状态激活时哪个子状态是激活的,并能够自动返回到那个子状态,就可能避免重复同样的转换。如果CD播放机是在播放,那么按下“播放”它应该从曲目开始继续播放,但如果播放机暂停,那么它将一直暂停到再次按下暂停按钮。通过使用如图10.12所示的历史状态可以达到这个效果。历史状态由圆圈中一个大写字母“H”表示,并且只能出现在组合状态之内。到达历史状态的转换引起组合状态中最近的激活子状态再次成为激活的。于是,如果在CD播放机暂停时按下“播放”按钮,将沿忙碌状态上的自转换进行,终止在历史状态。这将引起一个到上个激活子状态的隐含转换,在这个例子中即暂停状态,如同所需要的那样。 图 10.12 历史状态在CD播放机暂停时,如果按下了停止按钮,随后按下“播放”,图10.12规定,CD播放机将回复到正在播放状态。如果要求它应该仍然是暂停的,即使已被停了下来的播放又要再开始,这可以通过将这个播放转换的末端从忙碌状态的边界延伸到历史状态来建模。这引发了一个问题,如果历史状态是忙碌状态的第一个激活子状态,将出现什么情况:根据定义,在这种情况下应该没有记忆的历史。在这种情况下,我们必须指定一个默认状态成为激活的。这可以通过从历史状态向需要的默认状态画一个无标注的转换实现,在这个例子中,默认的是正在播放状态。10.9 CD播放机总结图10.13显示了一个描述CD播放机的行为的完整的状态图,结合了本章讨论的许多要点。这个图源于图8.10和8.12的合并。在“未播放”状态中加入了一个初始状态,还加入了另一个历史状态,表示在没有CD播放时按下停止按钮没有作用,不会引起播放机的状态改变。对这个图进一步的扩充和修改建议作为本章后面的习题。 图 10.13 CD播放机完整的状态图10.10 实际中的动态建模本节举例说明一个有用的构造状态图的过程,并以此说明来自交互图的信息如何能够用以导出状态图。使用的例子是下面几段描述的自动售票机。售票机可以接受来自乘客的货币和其他输入,并且在成功交易结束时输出需要的车票以及要找的零钱。没有交易在进行时,机器显示信息“需要准确钱数”,或者“可找零钱”。显示的信息决定了下一位顾客为所选的车票或是必须输入所需要的准确钱数的货币付款,或是在输入超过所需要的钱数时机器能够找零。机器的界面由若干按钮组成,每个按钮对应给定的一种车票。如果用户按下这些按钮中的一个,机器就显示要输入的钱数,即车票的价钱。随着用户向机器输入货币,显示的数目就根据用户输入的数量减少。输入的钱数一超过或等于车票的价钱,就发生下面的两件事情之一。如果机器最初显示的信息是“可找零钱”,就输出需要的车票以及所需找的零钱。如果显示的信息是“需要准确钱数”,并且用户输入的恰好是所需钱数的货币,将只发售一张车票;如果用户输入了过多的货币,所有输入的货币都将返回。在交易开始,用户可以选择在选择车票种类之前先输入货币。如果在最终选择一种车票时,已经输入了足够为该车票付款的货币,就像前面的情况一样输出车票和找的零钱。如果输入的钱数少于所需车票的价钱,机器将显示剩余的费用并像上面的情况一样继续进行。在任何情况下,如果在30秒期间没有收到用户的输入,交易将终止,已经输入了多少钱都会返回给顾客。“取消”按钮也可以使用户能够明确地表明终止交易。10.10.1 状态机和事件序列状态图概括了一个对象能够接收的所有可能的事件序列。然而,要识别为准确建立对象的行为模型所需要的所有状态,有时相当困难。这里介绍的技术通过一次只考虑一个事件序列,逐步建立起所需状态图的完整描述,避免了这种困难。从交互图中可以获得多个各自独立的事件序列。到达一个对象的消息,如果按照对象接收它们的顺序组织起来,就构成了这样的一个序列。在该对象的状态图上,必须可能找到对应于各个这样的事件序列的路径。在构造状态图时,我们可以从选取一个序列,并定义一个只表示该序列的简单的状态图开始。然后,将更多的事件序列集成到这个初步的状态图中,用这种方法,可以用逐步的方式建立起一个完整的状态图。本节剩下的部分将以售票机的例子说明这个过程。将考虑售票机设计的一些典型事务并逐步地建立起一个完整的状态图。将不画出正式的对象交互图,因为这些事务中只涉及一个对象,即售票机本身,而且发送的消息只是由用户产生的事件序列。10.10.2 付款之前选择车票假如用户首先选择了特定种类的车票,然后相继输入了三个硬币。这三个硬币的总值超过了车票的价钱,所以机器输出车票以及需要找的零钱,并回到空闲状态,等待下一次交易。在这种情况下,机器接收到的是一个四个事件的序列:一个“车票(ticket)”事件,随后三个“硬币(coin)”事件。这些事件中的每个都有相关的数据,即所给出的所选车票的价钱和所输入的各个硬币的价值,但是我们将暂时忽略这些细节。通过假定每个事件对应于两个状态之间的一个转换,可以简单地为任何单独的事件序列画出一个非常简易的状态图;在实行中,我们在序列的开始和结束以及每对事件之间放上状态。在现在的例子中,我们得到图10.14所示的状态图。因为这只是一个初步的图,所以没有试图为这些状态加上标注。 图 10.14 售票机的初步状态图尽管这个状态机是所考虑的单个事件序列的精确的模型,但是还需要对它进行一些调整,才足以作为售票机的基础。首先,图10.14只定义了一次交易,然而售票机能够一个接一个地执行重复的交易。为了对此建模,我们可以合并图10.14中的第一个和最后一个状态。因为这些状态代表了没有正在进行交易的情形,我们将这个新状态标记为“空闲(Idle)”。其次,在上面示例的交易中,硬币的数目实质上是任意的。在一次交易中可以输入任意数目的硬币:需要的确切数目取决于所选车票的价钱和输入硬币的价值。需要用某种循环来表示输入任意数目硬币的可能性。这可以通过将图10.14中的三个中间的状态合成为一个带有自转换的状态实现。最初交易的状态图的一个改进版本如图10.15所示,其中并入了这些修改。这个图中只包含两个状态,它是由如上面所阐述的图10.14最初的五个状态导出的。机器从空闲状态出发,当用户选择车票时,沿着通向“付票款”状态的转换前进。在随后输入硬币的时候,可能发生两件事情之一。如果输入的钱币总量足够付清所选车票的价钱,则跟随的是回到空闲状态的转换。但是如果还需要更多的钱,就沿着付款状态上的自转换循环,并且必须输入更多的硬币才能继续处理。 图 10.15 改进的状态图图10.15使用了前面介绍的一些状态图表示法特征来阐明机器在这次交易中的行为。首先,各种消息附上了参数以传送车票的价钱和输入机器的每个硬币的价值。其次,有两个从付款状态出发的标注为“硬币”的转换。它们由一个非形式的监护条件区分,该条件检查是否已经输入了足够的钱为所选车票付款。最后,在回到空闲状态的转换上还显示了动作,如果已经输入了足够的钱,机器将执行该动作。在这个例子中,我们假定机器能够输出需要找的零钱。10.10.3 选择车票之前付款我们现在已经处理了一种可能的交易,并构造了一个初步的状态图。下一步是考虑第二种交易,并尽可能将它集成到已有的状态图中,并在必要时扩充状态图。除了首先选择车票,售票机的说明还允许用户先输入钱,再选择需要的车票。对应于这个交易的事件序列是以若干硬币事件开始,然后在接收到车票事件时输出车票和找的零钱,并结束交易。我们可以假定,交易从图10.15中已经标识的空闲状态开始。然而,第一个事件存在一个问题,因为图10.15中不包含从空闲状态出发的标注为“硬币”的转换。因此需要用一个适当的转换扩充状态图,通向一个新状态。同前面的情况中一样,可以输入任意数目的硬币,而我们可以利用这个新状态上的自转换对此建模。最后,会接收一个车票事件,而机器将输出票和找的零钱,并返回到空闲状态。图10.16所示的状态图增加了这些内容。 图 10.16 合并第二个交易10.10.4 集成交易至今所考虑的两个交易互为镜像。在第一个交易中,在输入硬币之前选择车票类型,而在第二个交易中,输入所有硬币之后选择车票类型。然而,售票机的说明书允许第三种可能性,在选择车票类型之前先输入一些硬币,不过还需要更多的硬币补足票价。图10.16并不满足这种可能性:一旦已经输入硬币,选择车票类型将使状态机回到空闲状态,没有输入更多硬币继续交易的可能性。需要的是增加一个标注为“车票”的转换,从图10.16中的“插入硬币”状态出发。这个转换应该用一个条件和已有的转换区分开。必要的条件和在用户付票款时区分插入硬币是否会引起机器回到空闲状态的条件相同,即是否已经输入了足够的钱数付清所选车票的价钱。这个新的转换可以通向一个新状态:为了满足售票机的说明,新状态将必须允许输入硬币直到总计达到票价,随之将售出车票,机器返回到空闲状态。然而,这正好是已有的“付票款”状态提供的行为,所以可以定义新的转换到达这个状态,如图10.17所示。状态的名字也有所改变,以便更准确地反映它们之间的相关差异,即用户是否已经选择了车票类型。 图 10.17 集成两个交易从任何给定的事件序列对应于穿过状态图的一条连通路径的意义上来说,图10.17的状态图概括了在以输出车票结束的交易期间用户能够产生的所有可能的事件序列。在一个交易中,可以输入任意数目的硬币,车票类型可以选择一次,并且这些事件能够以任何次序发生。然而,售票机的用户的行为并不总是如此明智的方式进行的。容易想象,一个用户选择了一种车票,在输入硬币之前又改变了主意,又选择了另一种车票。图10.17的状态图不允许这种可能性。一旦选择了车票种类,机器就进入“已选车票(ticket selected)”状态,而且并没有从这个状态出发的车票转换。在用户实际能做的和状态图所规定的之间在这里似乎存在一个矛盾。解释这种现象的一种方法是注意尽管用户实际上可以重复地按下车票选择按钮,但是这未必意味着机器接收了“车票”事件。例如,情况可能是一旦进入了“已选车票”状态,就使车票选择按钮无效,并且只有再次到达空闲状态时才重新激活。如果不是这样,就需要在状态图中给出额外车票事件的明确说明。这能够以多种方式实现,如果这样的事件是不被接受的,可能通过引入一个错误状态实现,或者如果是允许的,用“已选车票”状态上的一个自转换实现。当然,在实际例子中,在这些选项之间的选择将由正在建模的售票机的实际行为决定。图10.17准确地模拟了售票机的基本行为。为了完成模型,表明能够终止交易的其他方式,即由于按下取消按钮或者由于超时而终止,以及在机器要求输入恰好等于票价的情况下的其他行为,都是必要的。在完成售票机状态图之前,将先介绍对这些特征建模所需要的符号。10.11 时间事件如果30秒都没有收到来自用户的输入,售票机将超时:当前交易将被终止,输入的钱将返回给顾客。超时应该作为一个转换建模,因为它将改变售票机的状态,从交易中的一个中间状态回到空闲状态。然而,应该用什么事件标注这样一个转换并不明显:毕竟,要点是这样的转换必须在没有检测到事件的时候精确地激发。UML定义了专门的时间事件,可以用于这些情况中。图10.18显示了一个转换,在进入“未选择车票(no ticket selected)”状态30秒后将激发。这可以如此理解,想象每个状态的一个隐含活动是执行一个计时器,在每次进入该状态时复位。一旦计时器已经运行了时间事件所规定的一段时间,就激发从状态出发的标注有时间事件的转换。在图10.18中,注意,每次输入硬币时计时器复位,因为自转换被认为是状态的改变,并触发状态的入口动作。 图 10.18 时间事件在关键字“after”之后,可以给出任何一段时间作为参数。时间事件的另一种形式由关键字“when”后跟一个指定的时间点组成,这定义了一个在到达规定时间时将激发的转换。10.12 活动状态在图10.17中,两个转换在交易顺利完成后回到空闲状态。在每种情况中,都有必要检查机器是否能够退回任何需要找的零钱。如果可以,应该输出找的钱和车票,如果不能,应该退回输入的钱。这个行为可以用一对具有适当监护条件和动作的转换表示,但这些将不得不在每个回到空闲状态的路线上重复。为了避免这种重复,可以使用活动状态(activity state)简化状态图的结构,如图10.19所示。活动状态表示对象执行某些内部处理的一段时间。照此,它在状态图上表示为只包含一个活动的状态。在图10.19中,只要顾客对一个交易的输入一旦完成,活动状态就成为激活的,对应于机器计算它是否能够返回为完成该交易所需要找的零钱。 图 10.19 活动状态活动状态表示的内部处理通常不能被外部事件中断,在这些情况下,离开活动状态的唯一转换将是完成交易。在图10.19中,有两个这样的转换,通过表明是否有找零头的监护条件区分。在每个情况下,这些转换还带有相应的动作。活动状态在状态图中应该慎用,因为状态图的目的通常是说明对象对外部事件的响应,而不是对内部处理详细地建模。然而,有时它们很有用,如在图10.19中,作为简化状态图结构的方法。10.13 售票机总结图10.20给出了售票机的一个完整的状态图。它合并了前面几节指出的各种要点,还显示出了用户在交易中间按下取消按钮的结果。为了减少说明交易由于超时或取消而中断所需的转换的数目,图10.20中包括了一个组合状态,其意图是对应于交易正在进行的时间段。 图 10.20 售票机的完整状态图10.14 小结状态图提供了可以在交互图上说明的对象的行为方面的规约。状态图表明了对象在整个生命期期间能够检测到的事件以及对象对这些事件的响应。一般地,对象展示出依赖于状态的行为。状态的不同由检测到一个由对象处于什么状态而决定可能有不同结果的事件来区分。检测到一个事件可以引起一个转换激发,对象从一个状态转到另一个状态。监护条件能够用于表示在特定时刻,一组转换中的哪个转换实际上被激发。动作说明了对象对事件的响应。状态的入口和出口动作分别等价于所有到来和离开转换上的动作。状态可以包含活动,活动发生在对象处于该状态的整个时间。活动可以被用户产生的外部事件中断。如果活动没有中断而结束,将跟随离开该状态的一个完成转换。组合状态可以用于简化复杂的状态图。从超状态出发的转换同等地适用于所有嵌套的子状态。到超状态的转换的结果通过超状态内的初始状态指定。状态图可以通过依次考虑源于对象交互图的各个事件序列来开发。首先开发一个简单的状态图模拟一个这样的序列,然后在考虑其他序列时再进行必要的扩充。10.15 习题10.1 图10.13说明的CD播放机在检测到下列事件序列后会处于什么状态?假定测试时一直有CD。(a) 初始化(initialize),装入(load)。(b) 初始化(initialize),装入(load),播放(play),停止(stop)。(c) 初始化(initialize),装入(load),播放(play),暂停(pause),播放(play)。(d) 初始化(initialize),播放(play),停止(stop),装入(load)。(e) 初始化(initialize),装入(load),暂停(pause),播放(play)。10.2 这个问题引用了图10.13中给出的CD播放机的动态模型。假如在播放机处于打开状态,抽屉中没有CD,并且抽屉是打开的,这时关掉播放机,然后马上再开机。在这些操作之后,CD播放机处于什么状态?CD播放机的抽屉的物理状态是什么?如果现在用户按下装入按钮,会发生什么?为了关闭CD播放机的抽屉,用户必须按下哪个按钮?10.3 画出图10.12的修改版本,对即使在按下“停止”然后按下“播放”时CD播放机应该仍然保持在暂停的需求建模。10.4 在图10.13的未播放状态中,是否需要从历史状态出发的默认转换?如果不需要,为什么?如果需要,它应该到哪个子状态?10.5 本章CD播放机的描述中,对它如何记录哪个是当前曲目的细节还不明确。假定CD播放机有一个称为“曲目计数器(track counter)”的属性,其行为如下。抽屉中没有CD的时候,曲目计数器置为0。当检测到CD时,曲目计数器置为1;这在检测到装入或播放事件后实际关闭抽屉的时候发生。无论何时进入忙碌状态,曲目计数器决定定位于哪个曲目开头,并从而确定播放哪个曲目。标注为“前进(forward)”和“后退(back)”的两个按钮允许用户手工调节曲目计数器。如果抽屉中没有CD,这些按钮不起作用。否则,按下“前进”曲目计数器递增,而按下“后退”减少。当CD播放机处于忙碌状态时,按下这两个按钮之一会使播放头立即移动到所要求曲目的开始。扩充图10.13中的状态图,以对此行为建模。10.6 在10.10节讨论的售票机的例子中,假如一旦选择了车票类型,车票选择按钮就无效,直到交易结束时才重新激活。另外,假如已经输入了足够购买所需车票的钱,硬币的投币口就关闭,只有在输出任何车票和找零头时才重新打开。用入口和出口动作在图10.20的状态图上明确表示这种行为。10.7 去掉活动状态,重画图10.20,并从清晰性和易理解性的角度对得到的状态图和图10.20进行比较。10.8 修改图10.20的状态图,使得在交易开始检查找零头的可用性,并显示一个适当的信息,如10.10节所说明的。10.9 重画图Ex10.9给出的状态图,去掉所有的组合状态,并用剩余状态之间的等价转换带替所有到达和从组合状态出发的转换。 图 Ex10.9 使用嵌套状态的状态图10.10找出一个接受但另一个不接受的事件序列,说明图Ex10.10中的两个状态图的含意不等价。假定序列从初始状态开始。 图 Ex10.10 两个不等价的状态图10.11 阅览室中的灯是由一个分别标记为“On”、“Off”和“Dim”三个开关的面板控制。“On”把灯打开到它们的最大亮度,“Off”关灯。还有一个中等的亮度,在演示幻灯片和其他投影材料时使用。“Dim”开关将亮度从最亮降低到这个中等亮度;再次按下“On”开关,将恢复最大亮度。画一个状态图对这个阅览室的照明系统的行为建模。10.12 窗口管理系统中的窗口能够以三种状态之一显示:最大化,占据整个屏幕;正常,在屏幕的给定位置上显示为一个给定大小的有边界的窗口;图标化,显示为一个小图标。当窗口打开时,它显示为一个正常的窗口,除非选择了自动最小化,在这种情况下它将被显示为一个图标。正常的窗口和图标可以被最大化;最大化的窗口和正常窗口可以被最小化,或缩小为一个图标。最大化的窗口可以恢复到正常大小,图标可以恢复到最小化之前的大小。图标和正常窗口可以移动,正常窗口还可以调整大小。无论怎样显示,窗口总可以被关闭。画一个状态图表达有关窗口显示的这些事实。10.13 下面给出了对自动柜员机(ATM)的行为的描述。产生一个描述其行为的状态图。列出你由于描述的二义性、不清晰或不完整而必须做出的假设。用户通过插入一个银行卡开始在ATM上的事务。假定该卡是机器可读的,用户被提示输入他们的个人身份号码(PIN)。一旦输入了这个号码,就呈现给用户一个菜单,包含下列选项:显示账户余额,取款并要收据,以及取款不要收据。如果用户选择了取款选项之一,将提示他们输入取款的金额,输入的金额必须是10的倍数。ATM向银行的远程计算机发送事务详细资料时验证用户的PIN。如果PIN是无效的,给用户重新输入PIN的选择,并重试所选的事务。如果新的PIN还是无效的,重复这一过程。一旦输入了三个无效的PIN,事务终止,并且机器将吞入用户卡。如果输入了有效的PIN,进一步的处理取决于选择的事务类型。对“显示余额”事务,在屏幕上显示余额,在用户确认之后,回到事务菜单。如果用户超过了可以从该账户提取的金额,取款事务可能失败;在这种情况下,显示一个错误信息,在用户确认后,回到事务菜单。否则,退回用户卡,输出钱,如果要求收据接着是收据。在要求用户输入的任一点,除了简单的确认,都提供了一个“取消”选项。如果选择了取消,退回用户卡,并终止他们和ATM的交互。10.14 一个简单的数字手表包括一个显示小时和分钟的显示器,小时和分钟之间由一个闪烁的冒号隔开,还提供了两个按钮(A和B),能够使显示器更新。(a) 为了给显示的小时数加2,应该执行下列动作,其中按钮B给小时显示加1:按下A;按下B;按下B;按下A;按下A。画一个简单的状态图准确表示这个事件序列。(b) 在上面的交互中,显示的小时可以增加任何需要的数量,并且每当需要时整个交互可以重复。重画状态图以并入这些泛化。(c) 为了增加手表显示的分钟数,可以按两次按钮A,接着重复按下按钮B,每次给显示的分钟数加1。为这个手表画一个完整的状态图,并入对显示的小时和分钟的更新。为你的状态图中的状态给出有意义的名字,并对任何标注为“按下B”的转换加上适当的动作。(d) 这个手表随后被增强,并入了闹钟,而下面的交互被提议作为设置闹钟时间的方法:按下A;按下A;按下B (重复);按下A;按下B (重复) ;按下A。意思是用户快速地连续两次按下按钮A,像鼠标的“双击”一样。解释这个建议将如何向数字手表的状态图中引入非确定性。说明你怎样通过向状态图引入额外的状态消除这种非确定性。10.15 画一个状态图总结下面对Java线程的生命周期中可能出现的一些事件的描述中给出的信息。当线程被创建时,它并不立即运行,而是停留在New Thread状态。当线程处于这个状态时,它只能被起动或停止。除了start或stop之外,调用任何方法都没有意义,并且会导致抛出异常。start方法引起为该线程分配系统资源,并调用线程的run方法。在此时,线程处于Running状态。如果调用的线程的sleep或suspend方法,线程将变成不可运行的。Sleep方法有一个参数指定线程休眠的时间长度;当过去这么多时间,线程又开始运行。如果调用了suspend方法,那么线程只有在调用resume方法时才能再次运行。线程可以以两种方式死亡。当它的run方法正常退出时它自然死亡。在任何时候通过调用它的stop方法也可以杀死线程。
展开阅读全文
相关资源
相关搜索

最新文档


当前位置:首页 > 管理文书 > 施工组织


copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!