Web网站往往具有复杂与高度动态的特点。为了让Web应用在短时间之内开始运作,开发周期应该尽量地短。许多时候,开发者直接进入编写代码这 一阶段,却不去仔细考虑自己想要构造的是什么样的网站以及准备如何构造:服务器端代码往往是毫无准备的即兴式编写,数据库表也是随需随加,整个应用的体系 有时候呈现一种无规划状态。然而,只要我们运用一些建模技术和软件工程技术,就能够让开发过程更加流畅,确保Web应用将来更容易维护。
UML(Unified Modeling Language,统一建模语言)是一种通用的可视化建模语言,用于对软件进行描述、可视化处理、构造和建立软件系统的文档。UML适用于各种软件开发方 法、软件生命周期的各个阶段、各种应用领域以及各种开发工具。UML能够描述系统的静态结构和动态行为:静态结构定义了系统中重要对象的属性和操作以及这 些对象之间的相互关系;动态行为定义了对象的时间特性和对象为完成目标任务而相互进行通信的机制。UML不是一种程序设计语言,但我们可以用代码生成器将 UML模型转换为多种程序设计语言代码,或使用反向生成器工具将程序源代码转换为UML模型。
本文介绍用UML为Web网站建模的一些方法。全面采用UML技术是一个复杂的过程,但UML的某些部分很容易使用,而且它能够帮助你用更少的时间构造出更好的系统。
为了示范UML在网站建设中的应用,本文将构造一个支持无线用户、提供各个地区天气报表和交通流量报表的网站。本文不准备详细介绍UML本身。但为了方便起见,附录中简要介绍了常见的UML符号和术语。要了解更多有关UML的信息,请参见文章最后的参考资源。
不论你是从头开始构造网站、移植网站还是增加某个重要的功能,为了确保设计决策的最优化,进行一些先期规划是必要的。如果你和其他人协作完成一项工程,就工作总量及其分配达成明确的共识具有不可估量的作用。在规划期间,你应该努力对系统的以下方面形成正确的认识:
- 用户和角色。
- 应用需求。
- 各个界面之间的转换流程。
- 要用到的工具和技术。
2.1 用户
了解使用系统的用户是很重要的。不仅系统分析要求你接触一些用户(通过问卷调查、email,或者面对面交谈),而且你经常还要让系统能够控制 不同的用户角色和权限。通过对用户进行分类并了解他们的需求,你就可以找出线索来确定数据库的安全机制、功能限制方法、用户界面分组、培训和帮助需求、对 具体内容的需求,甚至还可以从侧面了解到潜在广告客户的分布。
![](http://www.4oa.com/Article/UploadPic/2005-8/200582912381705.gif)
图1:参与者/角色 层次图
上图显示了几组不同的网站用户(在UML中称为Actor,即参与者)。在这里,最普通的用户类型(“Site User”)位于图的顶端,实线箭头表示generalization关系(“泛化”关系,参见本文附录说明,下同),它表示Site User又可以具体分成两类用户:Guest,Registered User。这两类用户共有的特征在“Site User”参与者中说明,而Guest和Registered User各自私有的特征则在对应的参与者中说明。通常,你可以直接为参与者加上说明文档,无需单独编写说明用户的文档,但具体与你所用的UML工具有关。 在本例中,Registered User又可以细分为Wireless User和Administrator两种类型,系统对这些用户的处理方式应有所不同。
2.2 定义需求
在正式开始编写代码之前,你应该对准备构造一个怎样的系统有一个清晰的认识。虽然在编写代码的同时也可以逐步完成这一工作,而且这种做法也很有 吸引力,但借助图形和文字资料事先集体进行讨论效率要高得多。为网站编写详细的需求说明往往不那么合算,但你应该有时间画出几个草图、写下几段注解去说明 网站准备提供的服务。这就要用到Use Case图(用例图)。Use Case可以看成一组功能——它可能对应网站上的一个页面、一个必须编写的程序,或者网站上可能发生的一个动作(比如,验证用户登录,改变用户的配置文 件,清除过期的帐号,等等)。下面就是一个能够帮助你规划网站的Use Case图。注意,该图并没有显示出网站的所有Use Case,通常我们需要多个Use Case图才能描述完整的网站功能。
![](http://www.4oa.com/Article/UploadPic/2005-8/200582912381394.gif)
图2:Use Case图
即使是在这样一个简单的Use Case图中,我们也能够轻松地表达出大量的信息。例如,include关系说明两个Use Case包含同样的身份验证功能;extend关系说明天气页面可能以WML或者HTML格式显示;generalization关系说明各个具体的表现 过程将遵从“Render HTML Page”或者“Render WML Page”所描述的基本行为规则以达到维持统一的风格效果和统一宏观行为模式的目的。
上图也显示出无线用户能够访问网站中其他用户不能访问的某些区域。在这个Use Case图中,只有无线用户能够访问交通流量报表。这是因为我们已经得知只有在旅途中的移动用户才需要交通流量报表,而且不想再花时间把交通流量报表制作 成其他标记语言形式。由此,“Get Traffic Report”Use Case不需要分成WML和HTML两种显示形式,它可以直接包含“Render WML Traffic Report”这个Use Case。
一般地,你应该为这些Use Case加上简单的说明。具体地说,你应该描述每一个Use Case里将要发生什么,谁可以使用它,它如何启动、如何停止,以及某些时候可能发生的特殊事件(称为variation,即变化)。
2.3 用户界面组织
在制作Use Case的过程中,你会得到一些指示网站需要哪些用户界面的线索。也许你早就有了设计某些页面的绝妙主意,但Use Case帮助我们从另外一个角度来看问题。用户是否确实需要那么多的界面?某个页面是否过于复杂?网站的导航设施是否简单易用,即从主页访问常用服务是否 很方便?在勾画界面草图、制作网站原型之前,你应该先在Use Case图中解决这些问题。
当Use Case逐渐清晰时,我们就可以开始勾画出网站的大致结构。有些人会强调说页面和文件应该用相应的构件图(Component Diagram)建模,其实类图(Class Diagram)工具也很方便。请参见下图:
![](http://www.4oa.com/Article/UploadPic/2005-8/200582912382187.gif)
图3:用户界面及其布局
在上图中,各种网站服务被捆绑到了不同的网站区域:
- / - 网站的根
- /common/ - 公用的图形、脚本、CSS文件等
- /maps/ - 地图数据
- /traffic/ - 交通流量报表
- /weather/ - 天气报表
该图还显示了在页面之间传递的参数。regionId是一个很重要的参数,它代表着用户感兴趣的地区(可能是一个国家、城市或者省份)。 regionId在页面之间传递地区信息,使得用户能够从指定地区的天气报表跳转到交通流量信息。至于网站的common区域,你可以看到指针指向的是整 个包(package)而不是区域中的单个文件,这是一种减少混乱的简化方法,因为所有其它的包都要用到大部分(如果不是全部的话)/common/区域 中的文件。
用户界面布局图能够帮助你避免网站混乱,它对于规划网站是很有用的。而且,一旦确定了一种有效的网站结构组织方式,它还可以作为一个固定的模式在多个网站上应用。
2.4 工具选择
对于小型网站,选择工具和技术相当简单。特别是由于投资的原因,只有少数几种工具组合才具有现实意义——Apache,MySQL或者 PostgreSQL,PHP、Perl或JSP/Servlet。当前最流行的组合是Apache + PHP + MySQL,有许多低价位的Web托管服务支持并主要集中在这种工具组合上。而对于规模较大的网站,在投资应用软件之前,它必须对各种工具进行更严格的评 估和测试。下面是一个构件图的例子,它可以用来说明网站的体系结构。这个图形虽然简单,但它已经描述出了当前大多数网站的体系结构,对于你的网站,重新制 作该图可能也没有必要,因为再也没有什么与众不同的内容值得加入这个图形了。
![](http://www.4oa.com/Article/UploadPic/2005-8/200582912382606.gif)
图4:网站体系结构图
讨论软件的整个生命周期已经超出了本文的范围,但应该指出的是,建立应用原型和界面模型应该在这个时候就开始。务必记下有关网站结构和页面布局 的一些想法,因为最终你会想要为布局(菜单,导航条,页面整体布局等)编写一些公用的代码。另外,如果你正在转到新的工具和技术,建立原型的工作能够让你 确保设计的可行性,确信已经就新工具的使用对开发组成员进行了足够的培训。
三、设计阶段
设计阶段应该与分析阶段交迭。一旦对自己所要构造的系统有了较多的认识,你就应该开始拟定设计思路。先100%地分析系统再进入设计阶段是没有 意义的。需求总是不断地发展,而设计本身有时也会推动需求的发展(反之亦然)。所有的开发者都在进行某种类型的设计——只不过有些开发者直接以编程代码的 形式进行设计。虽然这也能够完成任务,但它使得管理复杂工程和在工作组之内分配任务变得非常困难。先花一点时间通过设计图构造系统模型,以后你将获得巨大 的回报。
3.1 为未来而设计
许多开发者花费在代码调试和改写上的时间超过了编写代码的时间,如果从一个以上网站的建设来看这个问题,情况就尤其严重了。好的网站设计能够以 结构、组织方式和代码重用的形式应用到多个网站上。然而,如果代码只是匆匆忙忙堆砌而成,从现有代码长期获益的机会就减少了。要对网站进行设计规划,一种 很有效的方法是画出类图(Class Diagram)。下图显示了类图通常要用到的许多重要关系。
![](http://www.4oa.com/Article/UploadPic/2005-8/200582912383542.gif)
图5:类图
说明如下:
Renderer类是一个抽象类(用斜体字显示)。这意味着Renderer类不能直接使用,程序只能创建其子类的实例(即new Region())。为了满足把页面内容显示到不同类型浏览器的需要,所有用来生成内容的页面都必须从Renderer类派生。 WeatherReport类创建并拥有Region对象,这通过代表聚合关系(Aggregate Relationship)的黑色菱形显示出来,它表示一个对象拥有并创建其他对象。 方法名字前面的加号(“+”)表示该方法是公用方法,可以被其他对象或者函数调用;减号(“-”)表示方法或者变量是私有的,只能由同一对象内部的成员函数访问。在PHP中方法和变量是公用的,但我们应该总是把变量看成私有,避免从对象外部直接访问变量。 HTMLWeatherReport类依赖于HTMLUtils类。依赖关系(dependency)表示一个类要创建另一个类的实例或者调用另一个类的方法。 类图中的每一个类应该注明:所有的方法(以及所有的变量,如有的话),方法的访问属性(public,private或者protected),方法的返回值类型,方法的参数,变量的类型。函数写在前面,如果类有变量的话,则一般随后在一个分开的方框中列出。
即使你所构造的不是一个面向对象的系统,你仍就可以用类图建立系统的模型。类能够方便地描述出各种包含关系和你所编写的函数文件。虽然此时类图不再显示继承、构成/聚合等面向对象系统特有的关系,但它可以用依赖关系描述出文件之间的调用关系。
3.2 运行时的系统模型
有些时候,我们需要显示出应用的各个部件如何在运行时协作完成任务。前面的类图显示了类之间的关系,但它没有显示出调用出现的次序,也没有显示 出来自一个函数的结果可能决定下一次调用的目标。为了在更动态的层面上描述系统,UML提供了许多其他类型的图。对于Web网站设计来说,情节图 (Scenario Diagram)特别有用。情节图分成两种:协作图(Collaboration Diagram),序列图(Sequence Diagram)。一般地,我们不会建立系统所有交互过程的模型,情节图只用来描述系统最复杂的部分,或用来概括出代码的一般调用模式。例如,我们可能要 示范特定的页面如何与验证用户身份的代码协作,或者要显示页面如何调用公用代码(工具性的框架代码)以保持统一的外观和风格。
协作图和序列图分别举例如下。
![](http://www.4oa.com/Article/UploadPic/2005-8/200582912383116.gif)
图6:协作图
上面的协作图显示了从Web网站获取天气报表的一般过程。注意该图忽略了一些不重要的方法,因为我们只对处理过程中的关键步骤感兴趣。你可以根 据编号“1”到“1.3.3.4”找出各个函数的执行次序。一些人喜欢以“1,2,3,……”形式对执行步骤编号,但一般而言,用 “1,1.1,1.2,2,2.1,……”的形式显示出调用栈的深度是一种更好的选择,这种编号方式能够更清楚地显示出程序的控制转换过程。例如,上图显 示出report()方法调用了WMLUtil以及Region对象中的许多方法:在通过一系列的查询和内容生成函数为指定地区生成报表之前,我们调用了 WMLUtil中的buildHeader(...)函数;最后我们调用的是WMLUtil模块的buildFooter(...),然后返回 report()方法,最后返回getPage()。你可以为协作图加上更多的细节说明,比如返回值、约束、条件等。
![](http://www.4oa.com/Article/UploadPic/2005-8/200582912383386.gif)
图7:序列图
就图形所传达的信息而言,次序图和协作图非常相似。事实上,许多UML建模工具能够从协作图生成次序图,或者相反。次序图与协作图的主要不同之 处在于:在次序图上,事件的发生次序一目了然,非常直观。另外,次序图中还可以加入生存周期和时间方面的详细信息,比如延迟、线程并发、对象的构造和删除 等。
在决定选用次序图还是协作图的时候,考虑以下几点有助于你作出最合适的选择:
- 如果要显示代码中与时间或线程密切相关的问题,选择次序图。
- 如果要显示对象之间的交互模式,选择协作图。
- 如果要显示几个或者大量对象之间的交互过程,选择次序图。
- 如果要显示少量对象之间的大量消息传递或交互过程,选择协作图。
3.3 应用部署的规划
正如本文前面“工具选择”部分所提到的,大多数Web网站的体系结构并不复杂。尽管如此,部署图(Deployment Diagram)在两个方面仍旧很有用:网站结构,文件组织。对于文件组织,前面讨论界面规划时已经提到它也可以用类建模工具进行规划。下面给出一个简单 的构件图供参考,但根据网站的需要和复杂程度的不同,你可能不需要它。
![](http://www.4oa.com/Article/UploadPic/2005-8/200582912384519.gif)
图8:构件图
3.4 设计原则
UML只是一个工具。如果使用得法,UML能够帮助我们轻松地构造出更好的网站。然而,要设计出优秀的网站,关键仍在于要有一个好的设计原则或理念。
“提高类的内聚力,减少不同类之间的联系”这一点在谈到好的面向对象设计原则时经常被反复引用。一个内聚的类包含那些在目标和作用域上都紧密相 关的行为和信息。它意味着你不应该把构造UI的代码和实现数学算法的代码混合到一起,你应该尽力把所有与用户紧密相关的信息封装到UserAccount 类。内聚式设计是一个重要的设计原则,原因有很多:它有助于减少类之间的依赖关系,使得设计更直观、更容易理解,方便了向其他开发者介绍整个设计,减少了 开发者同一时刻需要操作的类的数量,等等。例如,如果你要改变网站的用户身份验证机制,只修改单个文件中的一个类无疑要比修改多个文件、多个类更加方便。
“减少不同类之间的联系”意味着使类或者文件之间的交互减到最少。它不仅使得整个设计容易理解,而且也方便了代码的维护。请考虑下面这个例子:
![](http://www.4oa.com/Article/UploadPic/2005-8/200582912384816.gif)
图9:设计实例A
除非深入了解了上述各个类的用途,要估计这些类的内聚程度是不可能的。然而,从这些类之间的关系可以看出,这个设计方案已经成功地减少了不同类 之间的联系。类之间的交互被减到了最少,从而使得系统的行为很容易理解。更重要的是,修改任意一个类时受影响的类数量都减到了最少(例如,修改D类只直接 影响B类)。另外,要访问D类中的功能,我们无需知道任何有关E、F或G类的情况。作为比较,请考虑下图:
![](http://www.4oa.com/Article/UploadPic/2005-8/200582912384818.gif)
图10 设计实例B
显然,在这个设计实例中,类之间的联系是相当紧密的。一旦对D1类作了修改,为了检查这种修改对其他类的影响,我们必须对其他类进行广泛的测试。
只有在实践中不断锻炼才能避免出现过于复杂的设计,但注意以下几点有助于达到这一目标:
- 提高类的内聚力。不要把密切相关的功能分散到多个文件和类之中。
- 采用直观、有意义的名字。如果其他人不能了解类、函数或者变量的作用,不管类的结构是多么完美,整个设计仍缺乏直观性。过多地采用缩写词会影响设计的可理解性。
- 不要害怕改写代码。有些时候,在几个类之间移动一些函数能够大大地简化代码。
- 类应该保持紧凑、简洁。代码膨胀是类缺乏内聚力的一种征兆。过于庞大的类、模块或者文件往往缺乏明确的用途和目标。
- 让其他人复查你的设计。其他人可能有新的想法,或者为你指出你以为显而易见但别人却不能明白的问题。
- 在早期设计阶段不要考虑太多的性能问题。与一个笨拙的、为了昨天所出现的问题而优化的设计相比,一个简洁、经过精心调整的设计更容易进行性能优化。注意这并不是建议把性能问题抛到脑后,而是建议把细节优化问题留到工程后期考虑。
四、UML工具
下面是一些值得考虑的UML建模工具:
- Microsoft Visio:Visio Professional 2000现在开始提供内建的UML支持。如果考虑Visio绘图工具的其他各种用途,这是一个相当有价值的工具。如果你使用2000以前的版本,你可以在 这里找到Visio Stencil and Template for UML。
- Rational Rose:这是一个推荐使用的工具,但对于许多小型Web工程来说它显得很昂贵。有了Rational Rose这样的工具,改进和维护设计、从模型生成报表、在平行协作环境中与他人共同进行建模工作就很方便了。
- MagicDraw:一个基于Java的廉价UML建模工具。
- Together:与C/C++和Java联系密切,支持UML建模。
- Objecteering UML:一个免费的个人UML产品。
- System Architect:一个很受欢迎的高端UML建模工具,支持双向工程(Round-trip Engineering)。
五、附录:常用UML符号和参考资源
下面这个表格简要介绍了常用的UML符号和关系。要了解有关UML概念和各种面向对象术语的详细说明,请参见后面的参考资源。
符号 | 说明 |
![]() | Package 包。用来聚集和组织模型中的一个部分(Use Case,类,等等)。 |
![]() | Actor 参与者。它代表一个用户或者其他外部的激励器。 |
![]() | Use Case 用例。Use Case描述了系统某一部分的行为。一般地,Use Case记录对某个系统功能的需求,而这个功能由对动作或者事件的应答示范。 |
![]() | <> Relationship 包含关系。标注为<>关系的Use Case关系能够引入其他Use Case的功能。这是一种方便的分割Use Case、避免单个Use Case过于庞大的方法。 |
![]() | <> Relationship 扩充关系。标注为<>关系的Use Case关系能够在不重复现有Use Case的各种描述和需求的情况下,使现有Use Case的行为特殊化。 |
![]() | Dependency 依赖。正如其字面意义,它表示一个事物依赖另一个事物。这意味着一个事物了解另一个事物,并需要另外一个事物才能发挥功能。 |
![]() | Note 注解。在UML图中提供注解的目的是以简短的说明阐明图表的内容。 |
![]() | Component 构件。构件一般代表一个软件单元,它可能是一个DLL、一个执行文件,或者是一个数据库。 |
![]() | Node 节点。节点一般代表一台机器,这台机器具有运行一个或者多个系统构件的能力。 |
![]() | Class 类。UML中的类与面向对象编程中的类一样,即它定义并封装了一组行为和属性。类在运行时被实例化从而创建出对象。 |
![]() | Object 对象。对象是类的实例。例如,“MyClass myObj = new MyClass; ”创建了一个myObj对象。 |
![]() | Generalization 泛化。父类能够派生出(或称为特殊化)具有更多特殊行为的子类,此时父类即为子类的超类(或子类的泛化版本)。 |
![]() | Interface 接口。接口定义了一组可以从外部访问的行为。类、库、执行文件、数据文件都可以由接口来描述。接口本身并不实现任何功能,它只是和声明实现该接口的对象订立了一个必须实现哪些行为的契约。 |
![]() | Abstract Class 抽象类。抽象类不能直接实例化,但允许派生出具体的、有实际功能的类。 |
![]() | Association 关联。关联就是把两个或以上的类连接起来。你可以为两个类之间的这种关系提供更具体的信息。关联是两个或多个特定类元之间的关系,它描述了这些类元的实例的联系。在一个关联中同一个类可以出现在多个位置上。 |
![]() | Aggregation 聚合。聚合关系表示某个对象属于其他对象所有。 |