非正常的可视反馈可伴随着同步事件发生,而同步事件可由系统动作产生。但是,可以分别对它们进行建模。
在下节中将对这些特殊的事件依次进行论述。
6.1 异常处理建模
异常,由Meyer 定义[16],其作为运行时事件(run-time events)“可以造成一个例程(routine)调用的失败”。
另外,当例程由于一个状态不满足例程的契约(contract)而终止执行时,则该例程将会调用失败。这些定义会很复杂,因为它们需要更进一步的定义,例如例程的契约的定义,而这些进一步的定义也同样复杂。因此,要区别什么是一个调用失败,什么是一个异常并非显而易见的事情。除了正常的定义,这里的异常可能更类似于面向对象的程序设计语言(如Java[6]和C++[18])中那些异常的概念。
在用户界面方面,异常的重要方面是它们有时无法由异常处理(exception handler)完全解决,这时应用程序将会以可视化的方式向用户反馈某些地方已经出错(或者至少不像预期的那样)。事实上,异常处理一旦被激活,便会尝试着去解决那些由异常所标识的问题,而不会通知用户。而不幸的是,异常处理并不能解决每一种类型的问题。所以,应该将这些无法解决的异常通知给用户,或由用户来选择一种处理该问题的方案。
于是现在的问题便成了:如何进行与用户界面相关的异常处理方面的建模工作。
6.1.1 UI 异常处理的结构
在应用程序模型中有许多地方使用到了异常和异常处理。例如,对于一个在执行数据库查询过程中产生的异常,可由设计者选择在ConnectionUI 窗体的某个位置来显示一条错误消息。
在UML 标记中,异常可作为构造型《send》来进行建模,而构造型《send》标识的是从一个类的操作到一个异常处理类[2]的依赖关系(dependency)。图12 标识了从《control》ConnectionController 类的checkUser 操作至《exception》DatabaseFail 类的一个《send》依赖关系。此外,Booch 等人[2]提出的异常处理的分层结构则是使用构造型《exception》来进行标识。通常,那些未被捕捉到的异常将会被抛至的更高一层的异常处理中,直到它们被一个异常处理捕捉到为止,或最终到达分层结构中的顶层异常处理。如图12 所示,若一些异常未能被《exception》DatabaseFail 所处理,则其将会被《exception》Exception 进行处理。
任何类都有可能产生异常,这是因为类一般都具有方法(即例程),而这些方法所具有的契约(contract)都有可能被打破。尽管异常处理可以作为《control》类,但它们还不能完全像《control》类那样进行建模。而是,它们能够捕捉任何类型的类的异常(事件)。在这种情况下我们可以引入《exception》类。这些类的操作可从任何类的任何方法中进行调用,甚至是其它《exception》类的方法。
图12:UI 与事件处理之间的关系
当异常发生时,《exception》类的其中一个角色(role)就是可以作为《boundary》类的《control》类。然而在很多情况下,《exception》类不能对一个《boundary》类进行控制。例如,当异常处理需要一些来自用户的决定,退出(quit)或重试(retry),而原先的《boundary》对象并没有组件来处理这样的一次交互,那么就可以通过创建一个新的《boundary》对象,以提供在异常处理和用户之间的通信。