用统一建模语言来掌握Java面向对象编程的威力
by Hans-Erik Erikkson and Magnus Penker
这篇文章研究的案例提供给读者一点在现实中如何使用UML的感觉。处理一个图书馆书籍和杂志的借阅和存储,这个应用已经足够大通过一些真实的步骤来说明UML的使用的了。如果这个应用过于复杂的话,我们恐怕就不适合把它放在杂志里了。我们在一个分析模型中应用用例(use cases)和一项领域分析(domain analysis)来分析和描述这个应用。我们扩展它,放进一个描述技术解决方案典型部分的设计模型中。最后,我们用 Java语言编码它的一个模块(提供设计源码,包括完整的分析和设计模型在线下载,其文件格式为Rational Rose)。记住,这里仅演示了一个可能的解决方案。还有许多其它解决方案,但同时,没有一个方案能合适所有的情况。当然,一些解决方案将证明会优胜于其它的,但是只有经验和辛苦工作才会成就知识。
需求
通常,由系统最终用户的代表来编写系统需求说明书。对于图书馆这个应用,它看起来应该像这样:
(1)它是一个专为图书馆设计的系统。
(2)图书馆向读者借书和杂志,而读者好像图书馆的书籍和杂志一样都是被登记在图书馆系统中。
(3)图书馆需要处理新买的图书书名。流行的书籍买多册。当旧的图书和杂志过时或没用的时候,它们被移走。
(4)图书管理员是图书馆的雇员,他负责和客户(读者)交互,系统支持他的工作。
(5)读者能保留一本目前还没被登记图书或杂志,这样当图书(杂志)退还和被购买时候,这个读者被通知。 当读者(借用人)取出书(杂志)或通过外在的程序取消的时候 , 原有的保留被取消。
(6)图书馆能容易地建立、更新和删除存在系统中有关书名、读者(借用人)、图书借出、库存的信息。
(7)系统能运行在所有流行的操作系统上,包括Unix、Windows和OS/2。而且,系统要有一个现代的图形的用户界面(GUI)。
(8)系统对新的功能性扩充是容易的。
系统的第一个版本不须处理那些当保留的书名变成可得的时候发给读者(借用人)的信息, 也不必检查书名有没有迟来。
分析
此次分析尝试捕获和描述系统的所有需求,同时,在这个系统中建立一个定义包含键的全域类(在系统中被处理)模型。目的是提供一个可理解的东西,使开发人员与建立需求人员之间能够对这个系统互相通信。因此,分析是与使用者或客户协作典型指引。
开发者在这个分析阶段过程中不应该思考代码和编程的问题;它只是在设计下真正了解系统和需求的第一步。
需求分析。分析的第一个步要描绘出系统将会被谁使用和谁将会使用它。 这些就是各个用例(use case)和角色。用例描述了图书管理系统在功能方面提供了什么的功能:系统的功能需求。一个用例分析包括阅读和分析系统需求说明书,就好像和系统潜在用者 (客户) 讨论一样。
图书馆里面的角色确认为图书馆管理人员和读者(借用人)。图书馆管理人员是系统的使用者,而读者(借用人)则是是客户,客户是指那些从图书馆取出和保存图书和杂志的人,有时候一个图书馆管理人员或一个图书馆管理人员可能是读者(借用人)。读者(借用人)不直接地与系统互动;他们的行为由图书馆人员来代为工作。
图书馆管理系统的用例有:
<!--[if !supportLists]-->l <!--[endif]-->借书(Lend Item)
<!--[if !supportLists]-->l <!--[endif]-->返书(Return Item)
<!--[if !supportLists]-->l <!--[endif]-->约订保留(Make Reservation)
<!--[if !supportLists]-->l <!--[endif]-->除去保留(Remove Reservation)
<!--[if !supportLists]-->l <!--[endif]-->增加书目标题(Add Title)
<!--[if !supportLists]-->l <!--[endif]-->更新或删除书目标题(Update or Remove Title)
<!--[if !supportLists]-->l <!--[endif]-->增加书籍(Add Item)
<!--[if !supportLists]-->l <!--[endif]-->除去书籍(Remove Item)
<!--[if !supportLists]-->l <!--[endif]-->增加读者(借用人)(Add Borrower)
<!--[if !supportLists]-->l <!--[endif]-->更新或删除读者(借用人)(Update or Remove Borrower)
由于图书馆经常有多个通俗的标题,系统一定区分书的标题和项目的标题。
图书馆管理系统分析作成UML用例图,如图(-)所示。每个用例用文字叙述, 更详细地描述用例和角色之间的交互。这文本经过和用户/客户讨论后定义的。
<!--[if !vml]-->
<!--[endif]-->图(一)
所有的用例的描述都在线;借书(Lend Item)用例描述如下:
1、如果读者(借用人)没有保留:
<!--[if !supportLists]-->l <!--[endif]-->书标题被识别。
<!--[if !supportLists]-->l <!--[endif]-->书标题的一个可得的栏目(项)被识别。
<!--[if !supportLists]-->l <!--[endif]-->读者(借用人)被识别。
<!--[if !supportLists]-->l <!--[endif]-->图书馆借这个项(书)。
<!--[if !supportLists]-->l <!--[endif]-->新的借出被记录。
2、如果读者(借用人)有保留:
<!--[if !supportLists]-->l <!--[endif]-->读者被识别。
<!--[if !supportLists]-->l <!--[endif]-->书标题被识别。
<!--[if !supportLists]-->l <!--[endif]-->书标题的一个可得的栏目(项)被识别。
<!--[if !supportLists]-->l <!--[endif]-->图书馆借相应的书(项)。
<!--[if !supportLists]-->l <!--[endif]-->新的借出被记录。
<!--[if !supportLists]-->l <!--[endif]-->保留被删除。
除定义系统的功能需求之外,用例常用于分析检查是否适当的全域类(domain classes)已经被定义,而且它们在设计期间也用于确定技术解决方案对处理必需的功能是否是充分的。用例在顺序图中能被形象可视化,而顺序图详细说明用例的实现。
全域分析(Domain Analysis)。一个分析也要详细列举整个域(系统的主要类)。为了说明一个全域分析,阅读需求说明书和用例,同时,浏览那些 应该被系统处理的“概念“。或组织用户和领域专家进行一次集体研讨,尝试识别出所有必须要处理的关键概念(问题),连同它们彼此的关系。
这些图书馆管理系统全域类如下所列:BorrowerInformation(如此命名为了区分用例图中读者/借用人角色)、Title、 Book Title、 Magazine Title、 Item、 Reservation和Loan。它们连同它们的关系被编写成类图表,如图(二)所示。全域类套用《业务对象》来定义,业务对象是一套自定义的类对象,这些类对象是全域键的一部分。而且,它们在系统中应该系统中持久地保存。
<!--[if !vml]-->
<!--[endif]-->图(二)
一些类拥有UML状态图,来显示这些类的对象的不同状态,连同显示那些让它们改变状态的事件。在线的可用有状态图的类是Item和Title。用例借书(Lend Item,读者没有保留记录)的顺序图如图(三)所示。所有的用例的顺序图都在线可用。
<!--[if !vml]-->
<!--[endif]-->图(三)
当绘制顺序图时候,显然窗体和对话框作为一个界面提供给角色是必要的。在这一个分析中,充分明白到窗口界面对借、存和还项目(书)来说是必要的。详细的用户界面这里不再叙述。在这个分析中,为了区分窗体类和全域类,窗体类放在名为“GUI Package”包中,而全域类放在名为“Business Package”包中。
设计
设计阶段是通过解析所有的技术实现和约束关系来进一步详细说明分析模型。设计的目的要叙述一个可工作解决问题方法尽可能容易地转换为程序代码。
设计能被分为二部分:
框架(结构)设计。这是包(子系统)定义的高级的设计, 包括在包之间的依存(关系) 和主要的通信机制。自然地, 一个清晰而简单的框架(结构)是我们设计的目标,如果全部可能的话,这个框架包的 依存(关系)应该 很少,而且双向性依存(关系)应该 被避免。详细设计。在这里,所有的类被充分详细描述;对程序员(编写这些类的代码的人)给出清晰的说明书。UML 的动态模型常用于示范类的对象在特定的情形下行为。
*框架(结构)设计
一个设计优秀的框架对一个可扩展的和可改变的系统来说是基础。包所能涉及到的不是处理特殊的功能地区就是处理特殊的技术地区。区隔应用程序逻辑(全域类) 和技术上的逻辑是至关重要的,以便任一部分变化不会给另外一部份造成影响。一个目标是在包(例如,子系统)之间对依存关系识别和建立规则,以便在包之间没有双向性依存关系被建立(为了避免包之间变得彼此结合的太紧密)。另一个目标要识别对标准库的需要。今天,在技术区域开发库(网址)随便可得,例如:用户界面、数据库或通信等,但更多特定程序的开发库也被期待出现。
这个研究案例的包或子系统如下列各项所示:
用户界面包(User-Interface Package)。这些类以 Java语言 AWT包为基础, AWT在Java语言中是一个标准的库,专为写用户界面应用程序的。这个包和业务对象包(Business-Objects package)协作,业务对象包包含那些实际存储数据的类。UI包调用业务对象的操作方法来取回和插入数据。
业务对象包(Business-Objects Package)。包括来自分析模型的全域类,例如:BorrowerInformation、Title、Item、Loan等等。设计完整地定义它们的操作方法和添加对持久的支持。业务对象包和数据库包(database package)协作,因为所有的业务对象类必须从数据库包中的持久类继承而来。
数据库包(Database Package)。数据库包给业务对象包的其他的类提供服务,以便它们能被持久的保存。在目前的版本中,持久类将会在文件系统中以文件形式储存它的子类对象。
实用包(Utility Package)。实用包包含系统中其它包常用到的服务(功能)。目前, ObjId 类是这个包的唯一一个类。它被用到提到的持久对象,贯穿整个系统,包括用户界面、业务对象和数据库包。
这些包的内在设计如图(四)所示。
<!--[if !vml]-->
<!--[endif]-->图(四)
*详细设计
详细设计描述新技术上的类-用户界面和数据库包的类;同时,充实在分析过程中描绘的业务对象草图。类,静态和动态图在分析中经常当同一种图表使用,但是,现在它们被定义得更详细和更高技术层次。分析的用例描绘常用于检验设计中用例被处理的情况;顺序图常用于示例说明系统中用例在技术上如何实现。
数据库包(Database Package)。应用必须有持久存储的对象,因此一个数据库层必须被加入来提供这个服务。为了简单起见,我们把对象以文件的形式存储在磁盘上。有关储存的明细情况从应用来看是不可见的,其调用公有的方法例如:store()、update()、delete()和find()。这些方法是一个持久调用类的组成部分,所有需要持久对象的类都必须继承它。
在持久处理中有一个重要的角色就是ObjId 类,其对象在系统中任何涉及到持久化的对象都应用到它(不管是否对象在磁盘上还是已经读进在程序中)。ObjId,简易的对象,是在一个应用中一个众所周知技术来优雅地参考处理对象。借助使用对象标识符,一个对象的ID能被传递通过普通的Persistent.getObject()方法,而且对象将从持久储存中重新得到和返回。通常在每个持久类中通过一个getObject方法来被完成,其也执行必要的类型检查和转换。一个对象的标记符也容易当作一个方法之间的参数被传递(例如:一个搜索窗口,寻找一个特殊对象,能通过对象标识符传递它的结果给另一个窗体)。
ObjId 是一个在系统中被所有包(用户界面、业务对象和数据库)使用的一般类,因此,它在设计中已经被放在实用包中而不是放在数据库包中。
目前持久类的实现是可以被改良的。最后,持久类的接口已经定义成容易改变持久储存的实现。一些可供选择的方法可以是储存对象在一个关系数据库中,或储存在一个面向对象的数据库中,或使用Java1.1支持的持久化对象来储存它们。
业务对象包(Business-Objects Package)。设计的业务对象包是以分析的相应包(全域类)的为基础。类,它们的关系和行为是受保护的,但是,这些类更详细的被描述,包括如何实现它们的关系和行为。
在设计模型中一些操作已经被转化为几个操作(方法),而且一些已经改变了名称。这是正常,因为分析只是每个类的容量一个概略而设计则是系统的详细描述。因此,在设计模型中所有操作方法必须明确定义名字和返回值(由于空间限制,它们没有在图(五)中显示出来,但是它们在线可用)。注意下列在设计和分析之间的各项变化:
<!--[if !vml]-->
<!--[endif]-->图(五)
•系统目前版本没有检查一个项目是否及时返还,系统夜没有处理库存分类。因此,在Loan和Reservation类中,日期属性没有被实现。
•杂志和书籍的书名处理是一样的,除了借期以外,不须被处理。在分析模型中,杂志和书籍的字类就认为不需要了,仅是Title类一个type属性就说明了一个标题是杂志还是书籍了。在面向对象设计中没有什么,那就是说设计不能把分析简单化。
如果在未来的版本中以上两点的简化被认为是必需的就可以轻易的被移去。
来自分析模型的状态图在设计中也被详细说明, 显示在运行系统中状态如何被表现和处理。Title类在设计模型中的状态图如图(六)所示。其它对象通过调用addReservation()和removeReservation()方法能改变Title对象的状态,如图中所示。
<!--[if !vml]-->
<!--[endif]-->图(六)
用户界面包(User-Interface Package)。用户界面包是其他包的“顶端”。 它将在系统的服务和信息呈现给一个用户。如你所知,这个包是以标准的Java AWT( 抽象窗口工具包)类为基础的。
设计模型的动态模型已经被放到图形用户接口(GUI)包,因为所有和用户的交互都是经过用户界面开始的。再一次,顺序图被选择用来显示动态模型。用例设计模型的实现被精确详细的显示,包括类真实的操作方法。
顺序图实际上在一系列的反复中被建立。在实现(编码)阶段发现的东西导致更深一层的反复。图(七)显示设计模型中增加标题的顺序图。操作方法和信号正确表示像它们在线代码出现那样。
<!--[if !vml]-->
<!--[endif]-->图(七)
协作图可以用来代替顺序图,如图(八)所示。
<!--[if !vml]-->
<!--[endif]-->图(八)
用户-界面设计。在设计阶段期间一个特别的要完成行为是建立用户界面。在图书馆应用中用户界面是基于用例(use case)的,而且已经被分为下列部分,在主要窗口菜单中,每一个部分已经给出单独的菜单栏。
•功能。系统主要功能窗口,也就是说,借、还书项目和制定保留。
•信息。浏览系统信息窗口,有关于收集到的借用人和图书标题的信息。
•维护。维持系统的窗口,也就是说,增加、更新和名称、借用人和项目。
图(九)显示了用户界面包中一个类图的一个例子。这个图包含了典型AWT事件处理器。属性按钮、标题,编辑框没有被显示。
<!--[if !vml]-->
<!--[endif]-->图(九)
每个窗体通常呈现系统的一个服务和映射一个初始的用例(虽然不是所有的用户界面都要从一个用例映射而来)。建立一个成功的用户界面超越了本文所探讨的范围。欢迎读者研究一个在线的这个应用程序的UI代码,它使用Symantec Visual Cafe环境开发的。
实现
程序编写工作在系统构造或实现阶段开始。这个系统应用的需求指定说明这个系统能在多个不同的处理器和操作系统中运行,因此,Java被选择来实现这个系统。Java容易编制逻辑类到代码组件的映射,因为一个类一对一映射成一个Java代码文件。
<!--[if !vml]-->
<!--[endif]-->图(十)
图(十)举例说明,在设计模式中组件图(对于这个案例)包含了一个简单的逻辑视图类到组件视图组件的映射。逻辑视图中包也相应地映射成组件视图的包。每个组件包含一条连向逻辑视图中类描述的链接使它容易在两个不同视图之间导航(即使,在这个案例中,它仅是简单的使用了文件名)。在组件图中(除了业务对象包外)两个组件之间的依存关系没有显示出来,因为这些依存关系可以从得自于逻辑视图中的类图。
对于写代码,规范从设计模型中下列各项图表中总结而来:
•类规范:每个类,详细的显示必需的属性和操作方法。
•类图:类图是类的呈现,显示其静态结构和对其它类的关系。
•状态图:类的状态图,显示可能的状态和需要被处理的转换(连同触发这个转换的操作方法)。
•类对象中的动态图表 (顺序图、协作图和活动图) ,包括:图表显示类明确的方法的实现或其他对象如何使用这个类的对象。
•当开发者需要更多关于这个系统如何被使用的时候(当开发者感到,他或她对于详细的全面的上下文关系正在迷惑的时候),用例图显示了系统被使用的结果。
自然地,设计的缺乏在代码的阶段建没有保障。需求的新增或修改可能会被识别出来,这意味着开发者将必须改变设计模型。这是在所有的项目中都发生的。对于同步设计模型和代码很是重要,以便这个模型能作为系统最终文档被使用。
这里给出的Java代码例子是Load类和一部分TitleFrame类的。整个系统应用程序的Java代码在线可得。在研究代码时候,用心应用UML模型去阅读它,同时,尝试看UML结构如何被转换成代码。考虑这几点:
•Java包的规定等同于组件或逻辑视图类所属包的指定。
•私有属性对应于模型中属性的说明;同时,自然地,Java方法对应于模型中操作。
•ObjId类(对象识别符)被调用去实现联合,意味着这些联合正常地和这个类一样被存档(因为这个ObjId类是持久性的)。
清单1的例子来自于Loan类,这是一个业务对象类用来存储有关借出的信息。这个实现是直接了当的,由于这个类主要是信息存储的地方,所以代码很简单。大部分功能是继承自数据库包的持久化类。类唯一的属性是这个对象标识Item和BorrowerInformation类联合关系的,这些联合的属性也是被存储在 write()和read()操作方法中。
你可以测试显示在清单2中有关增加一个标题的顺序图(图七)上下文关系的addButton_Clicked()操作方法。连同顺序图一起阅读代码,就会明白其它的,更详细被图表描写的协作关系的描述。
设计模型中所有顺序图的代码都包含在源码(操作方法和类名都显示在顺序图中)中。
测试和部署
当代码编写结束时,UML的作用也不会停止。举例来说,用例图可以用来检验已完成的应用程序是否对它们支持的很好。而且对系统的部署模型和这篇文章的正文提供了一个便利的文档。
总结
这个案例各个不同部分的研究是由一班人设计出来的。他们用同种方式努力工作,这种方式他们已经应用到真实的项目去了。虽然不同的阶段和行为可能显得零散和分开和按一个严格的顺序来引导,这工作更是反复练习的。来自设计的课程和结论被反馈回分析模型中,同时,在实现中发现的东西在设计模型中被更新和改动。这是建立面向对象系统正常方法。