如何用状态图进行设计
 

2009-01-21 作者:Dr. Doron Drusinsky 著,Jill 译 来源:ccidnet

 

所有软件实质上都可以认为是一种特殊的状态机。状态机是一个公共术语,用来描述一个系统在某种条件下会做什么以及按什么顺序去做。就像一个程序员写的声明,必须按某种顺序执行,每条声明说明计算机状态的改变。

用可视化对象、状态图来描述系统行为的思想在设计方法中非常流行。状态图可用于系统和子系统行为建模,应用范围从简单商务应用到最复杂的通讯协议。

有两类系统行为:转换和交互,学习状态图的重要一点就是要理解这两种系统行为的区别。

转换(子)系统

转换(子)系统在调用时所有输入信息都已经准备好了,在某个计算过程后,会产生输出信息。如图2所示:

图1 一个简单的转换系统

转换系统的例子有:数据获取系统,音频压缩系统(软件和硬件),或者甚至是一个简单计算输入数值的平方根的过程。自顶向下分解是转换系统通常使用的设计方法论,因为它能够将复杂的输入输出关系分解为简单的、更易管理的关系。同样,传统的编程和系统级规格说明语言经过转换、修改,以适合自顶向下的功能设计方法。

交互系统

易于理解的一个交互系统例子是交通灯控制器。它的所有输入不可能同时存在,输入是没有终结的无穷序列。不可能编写一个转换系统来实现这样一个控制器。事实上,大多数控制器被定义为交互式的,而不是转换式的,其应用领域包括过程控制、军事、航空、汽车工业、DSP、ASIC设计、医药电子和类似的嵌入式系统。

图2 一个简单的交互式系统

其实每个系统都有交互式组件,因为一个系统几乎不可能脱离它的环境单独存在。反之,系统存在的原因就是为了与一些实体或环境中的实体合作或交互。这种合作通过发送、接受、识别、拒绝信号序列——这一系列交互行为来完成。

扩展状态图和Petri Nets与交互式系统有关(这是BetterState 的优势)。在本教程中我们称交互子系统为控制器。请不要将它与经典的控制理论相混淆。

状态图基础

状态图类别

在我们开始讨论状态图设计方法以前,分清楚两种常用的状态图是很重要的。状态图(State Diagrams)和扩展状态图(StateCharts)(后者是前者的一个扩展)。这两种类型我们都将讨论。BetterState4VisualBasic 和 BetterState Pro都支持这两种类型,不过,我们关注的主要是扩展状态图。

传统上,有限状态机(FSMs)和它们的图表副本、状态图用来详细说明和设计交互(子)系统。因为它具有高度可视化和直观的特性,所以被普遍接受。FSMs可以描述有限和无限序列,这一特性再加上它的可视化功能,使它成为电子工业最受欢迎的形式之一。

传统的状态图

状态图比相应的文本解决方案更易于设计、理解、修改和文档化。传统的状态图在过去几年一直没有大的变化,因此,在今天的交互系统应用中存在局限性。在本节的后面,你将发现扩展状态图弥补了这些缺陷。传统状态图的局限性包括:

传统状态图很单调,它们不适合自顶向下的设计和信息隐藏。而且,自顶向下的设计概念需要能够交互的软件,这样可以让用户通过操作和浏览复杂的设计来获取信息。

传统的有限状态机必须是完全顺序的。然而,应用程序并不是这样。现代的交互子系统(我们称它为控制器)必须能够对环境中大多数实体发出的信号进行响应。考虑一个电话应答机控制器在接第一个电话时第二个电话等待的情况。一个传统的FSM需要计算第一个和第二个打电话者所有可能的状态组合,这会导致我们通常所说的状态溢出现象。

扩展状态图

为了弥补状态图设计的这些限制,David Harel在他的论文“扩展状态图:解决复杂系统的可视化方法”中描述了扩展状态图。该论文发表在计算机编程科学(1987)上。在增加了层次、并行、优先级和同步等功能的同时,扩展状态图保留了有限状态图的可视化、直观等特性。

下文讨论可用于传统状态图和扩展状态图的基础设计方法。后一部分讨论的是扩展状态图特有的设计方法。

状态图符号

状态图的主要组成部分是状态和代表状态改变和转换的箭头。状态通过大量不同类型的符号图形化表示。这些符号包括圆、矩形、圆角矩形等。BetterState使用圆角矩形来表现系统状态。

系统状态

在Webster的New World Dictionary(新世界字典) 中对“状态”的定义如下:

“在给定时间、方法和行为的情况下,与某人或某件事相关的一组环境变量或属性集”

状态图用于图形化显示状态以及在任何时候的状态之间发生的交互。如图4所示的简单状态图,包括一个源状态,一个目标状态,以及这两个状态间的转换。

图3 简单状态图

处于如下情况时,系统在一段时间内只能响应一个可视状态:

1、在外部环境中等待某事件发生。

2、等待环境中的当前活动(如混合、洗、填充、计算)去改变其他活动。

这并不意味着我们的系统没有执行动作的能力,我们将在本章的后面讨论与动作相关的问题。不过,值得注意的是,系统在当前可视条件下的状态和所执行的动作不同。因此,一个状态必须呈现为系统的某种行为,这些行为是可见的,而且它们将持续一段有限的时间。

注意:在BetterState中,当你添加了一个状态,将显示一个对话框。在该对话框中你可以输入状态名,所需的动作类型(有关动作的详细内容将在后面讲述),并设置与状态相关的其他选项。有关这个问题的详细信息请参见BetterState用户指南,第四章 “使用扩展状态图”。

改变状态

一个系统有自己特有的管理自身行为的规则。这些规则指定在什么时候系统会从一个状态改变为另一个状态。一个有效状态改变称为转换或迁移(transition)。一个转换连接有关的一对状态。代表转换的符号是一条带箭头的直线。箭头方向表示转换的方向。

图4所示的状态图说明了系统可以从状态1转换到状态2。它还说明处于状态2的系统什么时候可以转换到状态3或回到状态1。

不过,根据这张状态图的设计,系统不能从状态1直接转换到状态3。另一方面,这张状态图告诉我们系统可以直接从状态3回到状态1。

注意:状态2有两个后继状态(状态1和状态3)。这在状态图中是很常见的。任何状态在不同的环境下都可以有许多相应的后继状态。

图4 简单的三状态FSM

图5告诉我们依赖于时间的系统行为的一些有趣信息。不过,它没有考虑非常重要的一个系统元素。系统的初始状态是什么?根据图6的系统模型,该系统将永远运行,并将不停的运行下去。而现实中的系统必须从某个状态开始运行。

图5 一个没有初始状态的简单状态图

在BetterState中,初始/开始状态用一个缺省的符号( )来表示。图6是一个和图6相似的状态图,它指定了一个开始状态。

图6 一个有开始状态的简单状态图

注意:一个简单的FSM可以仅有一个初始状态。但对于扩展状态图,由于要表现层次和并行关系,可能有多个开始状态,并且在任何时候都可能有多个状态机在运行。

条件和动作

要使我们的状态图完整,你可能想增加两个额外的选项:

· 选项条件 (conditions) 导致状态的改变;

· 选项动作 (actions) 表示系统在改变状态时将做什么。

注意:事件驱动的编程语言,例如在Delphi, Visual Basic, 和MFC中,事件会引发状态的改变。有关该问题的详细信息,请参见用户指南中的事件驱动编程部分。

转换条件是系统能够检测到的外部环境中的一些条件。它可能是一个信号,一个中断,或者一个到达的数据包。它可能是任何导致系统状态改变的东西,使系统从一个等待状态 x 改变到一个新的等待状态 y,或者从执行活动 x 改变为执行活动 y。

在改变状态过程中,系统可能执行一个或多个动作。它们可能是产生一个输出,或在用户终端上显示一个消息,执行某些计算等等。在状态图中,动作是为了响应外部环境,或执行计算,系统将记录结果以便响应将来发生的某些事件。

图7 有特定条件和动作的简单状态图

注意:在BetterState中,当你在两个状态之间添加一个转换后,会显示一个对话框,在对话框中可以定义转换的条件以及所需的动作。

设计第一个状态图

现在,你已经掌握了足够的状态图设计方法的基础知识,可以对系统行为建模了。我们的第一个设计是个小小的挑战,设计冰激凌销售机的逻辑,它应该能够检测到卖了三个蛋卷冰激凌。你可以先用笔和纸来设计,这样可以加深对前面所学东西的理解,当然,你也可以跳过这一步,直接使用BetterState。

注意:请记住教程第二章的目的是告诉你如何设计扩展状态图。我们只用了很少的篇幅介绍BetterState的用法。这部分将在第三章详细讲述。如果你想使用BetterState来完成这个设计,建议你先学习第三章,它将引导你逐步完成一个类似的设计。

在这个简单例子中,先添加一个初始状态,命名为Start,然后添加第二个状态,名为One Sold。One Sold状态指你的系统在卖出一个冰激凌后的状态。现在你可以从状态Start转换到状态One Sold。然而除非真的卖出了一个蛋卷冰激凌,否则你不希望系统发生状态转换(从状态Start转换到状态One Sold)。因此从状态Start到状态One Sold的转换条件为Cone Sold (卖出了一个蛋卷)。该过程将继续,直到三个蛋卷都卖出去了。在这个例子中我们没有使用动作,不过我们可以使用。可以为该设计加上一个动作,如每次卖出冰激凌后在收银机上记录销售额。

图8 冰激凌销售机计算机系统说明

到目前为止,我们已经讨论了状态图的原理。这些原理对状态图和扩展状态图都适用。第二章后面的部分主要讲述了扩展状态图的扩展功能。我们将围绕这些增强的功能,使你对BetterState Pro的设计能力有很好的了解。关于这些内容和其他有关扩展状态图特性的完整信息,请参见BetterState Pro用户指南。

设计层次结构

在复杂系统中,可能会有许多独特的系统状态。想在一个单独的图中把它们都画出来,即便可能也是相当困难的。分层结构是公认的设计和管理复杂设计的方法。分层就是把相关的状态放在一起。分层结构帮助人们抽象过程,它是现代计算机软件的基本特性之一。

在BetterState中,分层表示为把状态放到其他状态内部。图9是我们所讲述内容的一个例子。在这个例子中,分层的作用是将相关一组状态放在一起。高层的转换如下图所示,高层状态是状态1或状态2,转换可以实现。如果是状态2,应具体说明是状态3还是状态4。

图9 有两个子状态的分层状态,其中一个还有额外的子状态

在现实世界中,分层是如何应用的呢?让我们复习一下冰激凌机的状态变化的例子。

现在改变我们原来的条件,不仅检查卖了3个冰激凌,还检查在卖了一个或数个冰激凌后又卖了一个冰激凌容器。

图10使用分层结构修改后的设计

通过在状态One Sold和状态Two Sold外简单的增加一个新状态。我们创建了一个高层状态,它代表冰激凌容器的销售。条件Container Sold称为高层转换。在本例中,如果高层状态是状态One Sold 或状态Two Sold 并且满足 Container Sold的条件,高级转换就会发生。它的含义是如果卖出了一个或多个冰激凌,接着又卖出了一个冰激凌容器,系统将转换到Be Happy状态。

独立的控制线程

扩展状态图也提供了获取无序的输入事件的方法。这意味着一个状态开始时,它可能位于一个或多个控制线程的交叉点。控制行为的每个独立线程都类似一个状态机,独自运行,互不干扰。因此,这些控制线程可能会同时发生状态转换。(Independence)独立/并行(也称Concurrency)是一个非常强大的设计功能。有关使用Concurrency / Independence的完整信息和更多的示例。

为了阐明这个例子,我们扩展我们的冰激凌系统的模型,为它增加热狗(hot dogs)的销售。在这个例子中,除了统计冰激凌和冰激凌容器的销售数目,我们还要为那些有需要的顾客供应热狗。

你需要设计一个新的状态图,现在有两张单独的状态图。如图11所示:

图11 这两个独立的任务在工作时都要执行

并行为你提供了一个强大的设计工具,它可以显著地简化如图11所示的设计。如何用并行来设计呢?先增加一个状态,然后从Create菜单中选择Concurrency/Independence选项。在弹出的对话框中为新创建的“控制线程”命名,现在你可以在新的线程里设计状态图了。

图12显示了我们的冰激凌/热狗的设计,在Work状态中绘制独立的控制线程。

图12 两个控制线程

请注意,图12是多么易于理解。在图11中,我们有两个单独的状态图,很难说明这两个图的关系。而在图12中,很容易看出两个子状态图是相关的。这说明,在新的一天开始工作时、工作中——无论任何时候,你都可以在卖冰激凌的同时卖热狗。因此,在下班时,你应同时停止这两个活动。分层和并行让我们能够用更小和更易于管理的状态图来处理和解决复杂的任务。

可视化同步

可视化同步提供用图形表现独立线程之间相互交互的方法。可视化同步功能强大,因为基于消息的文本无法传递到底使用了哪种机制等信息;准确的行为只能从状态图中获得。例如,让我们修改图12中的设计,当冰激凌线程完成了happy sale,强制热狗线程找钱。修改后的状态图如图13所示。

图13 Be Happy状态和Charge Money状态同步

可视化的Switch/Case(决策多边形)

在传统的有限状态图中,一个条件,例如X,只能是两个可能结果之一。一般为True或False。而在具有可视化Switch/Case功能的BetterState的流图中,它可以是一个决策多边形,可以有多个结果,没有二选一的限制。理论上它可以支持任意数量的结果。

可视化Switch/Case表现为两个或多个目标状态中做选择。可视化Switch/Case中表达式的值决定能否发生转换。根据表达式的值,状态图转换为多个结果状态中的一个。

在扩展状态图中的任何状态,任何层次,设计者都可以增加一个可视化的Switch/Case ,它以图的方式表现了C / C++ /Verilog中的switch语句,Visual Basic中的 Case Select语句,和VHDL 中的Case语句。

图14是一个使用了可视化Switch/Case的例子。我们将在状态图中使用可视化Switch/Case来描述我们在雨天的行为。在图中,我们的行为将根据雨的大小而改变。如果雨下得很大,我们就回家;如果雨不大,我们就打开伞继续营业;如果是毛毛雨,我们就当作没下雨,一切照旧。

图14 展现我们在雨天的行为的扩展状态图

设计练习

这是一个有趣的练习。设计一个描述接电话系统的状态图。如果我正忙着,我将隔两个电话才接一个,除非电话是我老板打来的。同样,你可以使用老方法,纸和笔。或者直接使用BetterState。

在开始练习之前,我们为您提供了一些有用的提示:

1、你在忙的时候都在做什么?

2、你系统中可能出现的状态有哪些?开始、等电话、有一个电话?

3、当你拿起电话,它是你老板打来的吗?

你完成的接电话练习的行为模型可能如图15所示。

图15 接电话练习

一致性检查指南

当你初步完成状态图以后,你应该执行下面的一致性检查。

· 是否所有的状态都已确定?仔细观察系统,看看是否还有其他可见的行为或其他条件没有被包括。

· 是否能到达所有的状态,你是否定义了一些状态,却没有路径能到达它们?

· 你能从所有的状态退出吗?只有两种类型的状态不能退出,一个是历史状态,另一个是结束状态。这些状态的用法超出了本教程的范围;有关它们的详细信息请参见用户指南第四章。

· 对于每个状态系统是否都能正确响应所有可能的条件?这是设计状态图时最常见的一个错误。设计者验证了正确条件时的状态改变,但忽略了意外条件下系统的行为。假设设计者为系统行为建模,期望用户在他的终端上按下某个正确的功能键,使系统从状态1改变到状态2,按下另一个功能键,使系统从状态2改变到状态3。但如果用户两次按了同一个功能键,或者按了其他的键,那该如何处理呢?

临界区

临界区(在状态组中用粗线框标记的部分),重叠的两个或多个线程同时访问临界区中的状态。例如,在临界区中,线程A中的状态S1、S2与线程B中的状态S3、S4重叠,也就是说当线程A访问状态S1,S2时,线程B不能访问状态S3,S4,反之亦然。图16是展示临界区功能的例子。

图16 临界区

图16展示了我们到目前为止讨论过的扩展状态图的所有功能。在前面的例子中,我们使用决策多边形为雨天的行为建模。图16是这个行为的模型。如果下雨,将判断表达式,大雨是一种情况,如果雨下得很大,我们就回家。

图16描述了我们将如何回家。我们可能走路或乘出租车。注意并行线程Start Walking 和 Call a Cab可能同时发生。

在这个设计中,我们增加了临界区的设计。临界区为同时访问区域内的状态设置了限制。在本示例中,如果乘车就不能享受走路回家的乐趣。

至此,我们讨论了设计扩展状态图的基础知识。BetterState Pro的扩展状态图也支持历史状态,交互状态,和其他很多重要的设计和调试功能。请记住这些是针对BetterState Pro 和 Better State4VisualBasic的前端设计。一旦你的初始设计完成(或在设计过程中的任何时候),你都可以使用BetterState所带的代码生成器自动生成代码。有关代码生成器和BetterState特有的动画调试功能、分析功能的详细信息请参见BetterState Pro用户指南。

关于作者

Doron Drusinsky博士是BetterState软件工具的创始人,该工具应用扩展状态图方法论解决真实世界中的复杂应用。他曾经是David Harel教授的学生,Doron在扩展状态图领域有十几年的经验,并发表了多篇论文和文章。他现在是Integrated Systems公司的资深工程师,他继续在扩展状态图领域进行创新研究和开发BetterState产品。


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织