应用程序框架设计
许式伟
一、摘要
随着面向对象技术的发展成熟,已经出现了许多著名的应用程序框架,如在Windows平台下有MFC、VCL、OWL等;在旧的DOS系统下有Turbo Vision。在这里我希望通过我设计的“SW系统”来阐述我对设计应用程序框架一些想法。其中涉及的内容主要有:
1、 应用程序框架设计的基本内容
这一部分主要是讨论应用程序框架的必要性、可行性,以及设计应用程序框架的基本思路。
2、 SW系统的总体内容与实现
这一部分主要讨论作为一个应用程序框架,SW系统的总体结构和内容,并对SW系统一些重要的实现细节做出说明。其中主要有:SW系统中的窗口模型、属性、SW系统的RuntimeClass支持和序列化等。在最后,我们要分析经典应用程序框架的缺陷。同时也说明由SW系统向COM转变的必然。
3、 SW系统的新方向:基于COM(组件)思想的应用程序框架
这一部分主要介绍组件思想的基本内容。SW系统的对组件思想的实现。
二、应用程序框架设计的基本内容
一个应用程序到底有多少“骨头”,多少“肉”?这里所说的“肉”是指程序中用于解决问题的那一部分,而“骨头”是指“肉”所依附的程序框架部分,它们是为了实现与用户交互、使界面友好必需做的事情。
对于解决问题的逻辑,我们很难能够找到一个一般做法来简化这件事。只有在具体定位到某一具体的方向时,才有可能做到这一点。例如你要进行数值计算,可能会需要一个功能完善的数学包;你要进行图象处理,可能需要一个图象处理库;等等。严格的说,这些东西说不上是一个框架,只是一个个工具包(Utilities)。因为它们一般没有复杂的调用规则,函数之间相当独立。
对于应用程序与用户的交互,在DOS时代编程的人一定对此感触很深。DOS时期基本上程序与用户交互的动作都是自己完成的。这样做的结果往往不是觉得在界面设计上浪费了太多时间,就是觉得界面设计得不尽人意。各个程序的框架代码有大量的反复,但由于编程方法的局限,程序代码的重用效率往往比较低。
面向对象思想的成熟促使了种种应用程序框架的诞生。面向对象语言中,类的继存可以完成对大量现成代码重用的重用;动态束定(即虚函数机制)技术有效地将具体的实现代码延迟到设计阶段。而一种称为“事件驱动模式”的应用程序结构使程序的框架代码与实现细节彻底发生了分离。尽管现在的应用程序框架种类挺多,但它们在实现思想上相当一致:所有的这些应用程序框架都是“事件驱动模式”的一个应用。这也包括本文要介绍的SW系统。
“事件驱动”的核心自然是事件。从事件角度说,事件驱动程序的基本结构是由一个事件收集器、一个事件发送器和一个事件处理器组成。事件收集器专门负责收集所有事件,包括来自用户的(如鼠标、键盘事件等)、来自硬件的(如时钟事件等)和来自软件的(如操作系统、应用程序本身等)。事件发送器负责将收集器收集到的事件分发到目标对象中。事件处理器做具体的事件响应工作,它往往要到实现阶段才完全确定,因而需要运用虚函数机制(函数名往往取为类似于HandleMsg的一个名字)。对于框架的使用者来说,他们唯一能够看到的是事件处理器。这也是他们所关心的内容。
视图(即我们通常所说的“窗口”)是“事件驱动”应用程序的另一个要元。它是我们所说的事件发送器的目标对象。视图接受事件并能够对其进行处理。当我们将事件发送到具体的视图时,实际上我们完成了一个根本性的变化:从传统的流线型程序结构到事件触发方式的转变。这样应用程序具备相当的柔性,可以应付种种离散的、随机的事件。
由于Windows本身是基于“事件驱动”模型的。因而在Windows操作系统下实现应用程序框架有相当的便利。在事件驱动程序的基本单元中,事件收集器已经由Windows系统完成;事件发送器也已经由Windows完成了部分内容。之所以是部分而非完全是因为Windows是用C语言实现的,而不是C++。由于没有对象,Windows将事件发送到所谓的“窗口函数”中(尽管不是发送到具体的对象,但应该说这是面向对象方式实现的一个变体)。要感谢Windows做了这件事。确定事件的目标所要做的工作的复杂可能要超出我们的想象。我们要对此进行定性的讨论。
根据事件的发送路线,事件可以分为以下几种:
1) 位置事件(鼠标事件)
它的目标比较明确,即包含鼠标所在点的视图。不过这也有不适用的时候。在拖动时,往往希望鼠标消息的接收者是鼠标刚开始拖动时的视图,而非真正包含鼠标点位置的那个视图。正是考虑到这一点,Windows提出了“捕获鼠标消息”的概念。
2) 焦点事件(键盘事件、命令事件等)
对于焦点事件消息,情形就比较复杂。为了说明白这一点,需要引入模态视图、焦点视图等概念。
一个应用程序的视图结构通常由一个树型结构维护。在某个时刻,总存在一个视图,它的所有祖先视图不再接受事件,而它及其所有子视图仍然处理事件。这个视图被称为模态视图。它称得上是“活动着的根视图”。模态视图的一个典型例子是对话框。在它运行时是应用程序的其他部分都失去了响应。
什么是焦点视图?模态视图首先是一个焦点视图。模态视图的子视图中,有一个视图是被激活的(或者称,它获得了“焦点”),它也是一个焦点视图。如果它也是复合视图(存在子视图的视图),它也可以有一个子焦点视图。这个逻辑一直到一个被激活的简单视图(对于它,还有一个名称,叫“热点视图”)。“热点视图”的所有父视图、父视图的父视图等等,直到模态视图,构成一个“焦点视图”链。
总的说来,焦点事件首先发送给获当前的模态视图。在它不知道如何处理时,可能将事件顺着焦点视图链往下传。很多人在编程时往往发现某个事件处理函数总是不能够被执行。这种情况总是发生在焦点事件。其原因是由于祖先视图已经截获了该事件。如果能够对事件传递的过程理解了,应该怎样改写代码也就明确了。
然而在焦点事件上有太多例外。有些视图没有获得焦点是也希望能够处理键盘消息。典型例子是菜单,对话框中的一些按钮等。针对这些Windows中已经有一些操作惯例,同时Windows也是不可能完全确定目标视图的。
3) 广播事件
广播事件并不知道自己的目标,它的处理方式很简单,它将事件发送给所有视图。Windows并不支持广播消息。SW系统实现广播消息主要是为了逻辑的简化,例如关闭所有窗口、保存所有文件等,应用广播的概念是方便的。