本文从初始需求开始构建聊天室模型,以及对个案进行研究。在不同的开发阶段,分别要用到UML类图、时序图和状态图。这样,难免需要确定一致性问题,现在已经提出了许多仿真和方法,用来确保模型各个方面的一致性。
作者:中国IT实验室 来源:中国IT实验室 2007年8月27日
关键字:
5 状态图
状态图用来实现类定义背后的行为。它们可以在我们的SVM(状态虚拟机)[2][3]中执行,用于扩展状态图形式化的解释器是用Python编写的。
5.1 SVM约定
如果想要轻松理解状态图设计,就必须先了解SVM的一些约定。
SVM用扩展状态图形式化体系解释模型。加入了一些新的功能,尽管表现力没有加强2,但易用性却大大改进了。
尽管最初的状态图并不是模块化的,但仍然可以使用SVM实现基于组件的设计。对于聊天室模型来说这是必需的,象客户和聊天室这样的组件可以独立进行设计,但最终在系统中还是要一起工作的。组件通过导入可以进行复用。较大的组件导入一些小的组件(实例),作为它的一个状态。结果是,就象被导入组件的所有状态(分层的)和转换都是直接在这个状态当中编写一样。
SVM模型用文本文件编写。宏是在SVM中引入的一个概念。宏在SVM源文件里的MACRO节中定义。一旦定义后,在整个文本文件中就可以括在括号中使用。例如,定义PREFIX=state,[PREFIX]就可以用来代替字符串“state”,这样[PREFIX]1就等于state1。
后面就会用到其中的一些预定义宏。[EVENT(event, param)]会触发一次事件。它的参数是字符串event和可选列表param。[PARAM]用来检索在处理的事件的参数。它通常在监视哨单元或转换的输出中使用。[DUMP(msg)]打印调试信息或在文本文件中记录这些信息。
组件被导入的时候宏也可以充当参数。进行导入的组件要重新定义一部分或所有最初在被导入组件中定义的宏,也包括预定义宏。继续前面的例子,如果进行导入的组件确定PREFIX=mystate作为导入参数,被导入组件中的[PREFIX]1将会译为mystate1。
很容易就可以看出,这些扩展并没有增加状态图的表现力。
5.2 扩展状态图形式体系中的聊天室模型
Client、ChatRoom和Manager组件分别在独立的状态图中设计。如图四所示,Chat模型导入了五个Client实例,两个ChatRoom实例和一个Manager实例。同一类型的每个实例都有一个惟一的ID参数。由于可接收的事件集合是不相交的,因此不同类型的实例就可以拥有相同的ID。这个模型可以在SVM环境中仿真或执行。
Client组件如图五所示。最初,它处在nochat状态。每隔一到三秒(非均匀分布)会触发一次mrequest事件,通过管理器来重复连接聊天室,直到请求被接收为止(accept事件被接收)。uniform是一个Python函数,它返回某个区间内的随机实数值,randint返回随机整数值。事件的第一个参数给出客户的惟一ID。第二个参数给出目的聊天室(随机从1或2中选取)。然后,客户转移到状态connected,开始发送消息和接收广播。事件参数是作为列表发送的,[PARAM][0]访问第一个参数,依次类推。注意,当方括号之间的内容不是宏的名字或Python下标时,那它就是监视哨,遵循最初状态图形式化体系中的定义[4]。
用户定义的宏[ID]为每个客户指定一个惟一ID。定义ID=0的意思是缺省值为0。它可以由导入组件(在这里是Chat模型)变为一个惟一数。组件的ID很重要。既然整个系统在导入后可以被看作一幅大的状态图,每一个正交组件都能接收到所有的广播事件。这样,给特定客户发送事件的惟一方法是在参数列表中给出接收者的ID。每一个客户都要检查在处理事件之前,它的ID是否匹配。
与Client组件相比,ChatRoom要更复杂。它使用列表messages[ID]把到来的消息进行排队。这就意味着每一个有着惟一ID的聊天室都会有其自己的队列(如果ID=0,messages[ID]与message0相等)。如果当它正在处理前面的消息时(要耗时一秒钟),一条消息到达,新到达的消息就会加入列表之中。收到消息时的时间也被记录下来,这样即使消息进行排队,它的处理时间自到达时开始计算仍为一秒钟。
Manager组件仅仅只是中继消息。在聊天室接收客户时,函数rec_comm(client, room)在一个列表中记录连接信息。get_clients(room, client)查询列表并返回聊天室room中除了client以外的所有客户。get_room(client)返回client的房间ID号。
聊天室消息队列和管理器连接列表是变量使用示例。它们帮助记录模型的状态。严格地说,这仍旧是对最初状态图的扩展,状态必须明确地确定下来。讨论变量已经超出了本次个案研究的范围。
5.3 与类图之间的一致性
基于组件的设计应该严格符合图一中的类设计。再者,组件可以发送事件给接收者,但接收者却不能处理它。或者,发送者提供的参数可能会比需要的要少。这可能会造成严重的运行时错误。
自动检查所有方法调用的发送者-接收者之间的一致性,这样的程序可以写出来。而不是要编写如何在代码级由类型检查器和/或链接器来检查一致性。譬如,Manager接收事件maccept。这意味着它在类定义中提供方法maccept。在处理事件的状态转换输出和监视哨中,要用到[PARAMS][0]和[PARAMS][1],这样它就至少需要两个参数。检查器遍历整个Chat模型,找出仅由ChatRoom组件调用(异步地)的方法。调用[EVENT("maccept",[[PARAMS][0], [ID]])]提供了两个参数([PARAMS][0]3和[ID])。检查这条调用是成功的。
同样地,在模型中所有方法调用的一致性可以由状态图来检查。