引言 统一建模语言(Unified Modeling Language,简写为UML)是一种通用的模拟语言,它可以用于确定、展示和记录软件系统的设计过程。统一建模语言中的图形标记,尤其是用于面向对象的软件设计。它有两大优点:
(1)UML是国际软件工业界广泛认可的标准,它统一了对象模拟的标记和含义,使软件设计工具能发挥更大的功用,同时,现有的对象设计也能更容易地被重新使用。
(2)UML博采众长,设当地平衡了简洁性和具体化两个总之,UML已经成为一种单独的系统来演化,不像以前的多种标准的体系引起的问题。
所以,作为软件开发者,完全有必要学习、了解UML。本文就提供了一个案例研究,我只是想利用这个案例研究给大家一个对UML的感性认识,了解在现实世界中如何使用UML来编写应用程序。所以我想找了一个相对比较复杂的案例,找来找去,发现图书馆中处理借出以及预借书籍和杂志的应用程序是相当大的例子,足以说明UML如何在现实世界中使用。
我只是利用使用案例(use case)和讨论域分析来分析描述一个分析模型中的应用,我把它扩展成一个设计模型,用来描述技术解决方案的一个代表部分,最后,我们再用Java语言进行编码。但请记住,我给出的只是一种可能的解决方案,还有许多其他的解决方案需要您用聪明的头脑去发掘,而且这世界上也没有适合所有的情况的解决方案。当然,某些解决方案会比其他的要好,但那只有有了足够的经验和遇到的许多困难的事并解决之后才会积累下来知识。好,下面我们进入案例研究。
要求
一般情况下,是使用系统的最终用户的代表人来书写要求规范,对于图书馆应用程序,要求规范应该如下:
1、图书馆应用程序应当是图书馆的支持系统。
2、图书馆把书籍和杂志借给借书者(读者)的条件当然是读者应当在该系统中注册过,同样书籍和杂志也应当在系统中注册过。
3、图书馆处理购买新书或杂志的操作,畅销书或杂志应当多购几本,旧的书籍和杂志当它们过时或残破时就应适当把它们从书架上请下来。
4、图书管理员是图书馆中的职员,他的职责就是与顾客(借书者)打交道并通过该系统完成工作。
5、借书者可以预借一本当前不在图书馆中的书籍或杂志,当这本书被归还或被购入图书馆的时候,他就会接到通知;当借书者借到这本书或杂志的时候,预定就会被取消;也可以使用显示程序取消预借。
6、图书馆可以很容易地创建,更新和删除系统中的书名,借书者,借阅情况以及预借情况等信息
7、该系统可以运行于所有流行的操作系统,包括UNIX,Windows以及OS/2,它还应当有先进的友好的图形用户界面( GUI )。
8、该系统应当很容易使用新的功能扩展。
在本案例分析中,该系统的第一个版本不需要处理某个读者预借的书籍成为可借书籍时发送消息给读者的操作,也不需要检查某本书籍是否已经超时了。
分析
分析的目的是为了获得和描述系统中所有的要求,以及生成一个在该系统中定义关键域类的模型。其目标是在开发者与制定要求的人之间建立相互理解和沟通,因此分析是一种典型的与用户或客户合作的行为。在这个阶段开发者不应该考虑具体的代码或程序细节;这只是真正地理解要求和正在设计的系统的实际情况的第一步。
第一节分析要求
分析的第一步应当是判断该系统将被用于做什么以及谁将使用它。这分别是所谓的使用案例(use case)和行动者(actor)。使用案例描述了图书馆系统具体应当提供哪些功能,即系统的功能要求。一个使用案例分析过程包括阅读和分析规范,并且讨论该系统的潜在的用户(客户)。图书馆中的行动者是图书管理员和借书者,图书管理员是该系统的用户而借书者则是顾客,查看并且预订书籍和杂志的人应该是借书者,但是有时候也可能是一个图书管理员或另外一个图书馆。借书者并不需要直接与系统打交道,借书者的功能通过图书管理员来代理完成。
图书馆系统的使用案例是:
借书
还书
生成预订
删除预订
增加书目
更新或删除书目
添加书籍
删除书籍
添加借书者
更新或删除借书者
因为图书馆中的一种畅销书常常有好几本,所以系统必须把畅销书从普通书目中区别开来。
如果借书者没有预借书籍:
识别像要借的书的书名。
识别馆中本书目前可借的书籍
识别借书者
图书馆借出这本书。
登记刚刚发生的借出情况。
如果借书者曾预借某本书籍:
识别借书者。
识别预借的书名。
识别馆中本书目前可借的书籍。
图书馆借给借书人相应的书。
登记借出情况。
删除预借书籍的纪录。
除用于定义本系统的功能要求之外,使用案例还被用来分析检查适当的讨论域类是否已经被定义,以及在设计过程中它们是否能被用来确认该技术解决方案能够处理所需要功能。使用案例可以在序列图上看见,来实现一些技术细节。第二节 讨论域分析
分析过程中还要详细地列举讨论域(domain ,系统中关键的类),为了进行讨论域分析,需要充分理解规范和使用案例并且着眼于系统将要处理的"概念";或者与使用者及讨论域专家组织一次集体研讨会谈,尝试找出所有必须处理的关键概念以及它们之间的相互关系。
图书馆系统中的讨论域类如下:: BorrowerInformation(这样命名是为了区别于使用案例图表中的行动者Borrower),Title,Book Title,Magazine,Item,Reservation和Loan。图2是一张类图,标出它们的相互关系。这些讨论域类是用户自定义的类,指定该类的对象是关键域的一部分并将被持久的保存在系统中。
图二解释:域类结构。域分析要详细列举系统中的关键的类。对于每一个对象,它调用另外一个对象上的方法,就要在类之间加上一根线来说明它们的关系。每个用来表示类的矩形框被横向隔成三部分。最上面一格是类的名称,中间是类的属性,最下面一格是类的方法。两个类之间的关联用一根实现连接代表,象征一个对象调用了另外一个对象的方法。如果你仔细观察,还会发现在Loan和Item关联的Loan端的"0..1",代表那一端可取的对象数。
一些类用UML状态图来显示这些类的对象的不同状态以及能够使它们的状态改变的事件,如本文中的例子就是Item和Title。图3就是借书(Lend Item)的使用案例的序列图(借书者没有预借书籍)。
图三解释:借书情景的序列图。本情景给出的是使用案例的特定的过程,情景总是以一个行动者为开端,即系统以外的人。然后记录通过系统直到以所有的行动者角度来看这个的行动都已完成的完整的路线。用于标注一个情景的UML记号法就是序列图。这张序列图用来说明一个情景,即借书者没有预借某本书时的借书情景。
当我们给序列图建模的时候,很明显,我们需要给行动者提供一个与窗口或对话框的接口界面。在本案例分析中,为了把窗口类从讨论域类中区分开的,就把窗口类放入一个名为" GUI Package "的包中,而把讨论域类放入一个名为" Business Package "的包,应当明确需要提供借书、预借书籍和还书界面窗口,此时还不需要定义具体的用户界面。
设计
当已经考虑了所有的技术细节和限制条件,我们就可以进入设计阶段,设计阶段需要展开和细化分析模型。设计的目的是为了说明一种可以很容易地翻译成程序设计代码的工作解决方案。
设计阶段可以分成两部分:
1、结构设计这是非常高级的设计,说明在什么地方定义包(子系统),以及包与包之间的相互依赖与通信机制。自然,我们的目标是构建一种清晰而又简单的体系结构,包与包之间的依赖要少,如果可能的话,尽量避免双向的依赖。
2、详细设计所有的类都应描述足够的细节,来明确规定谁来编码这些类。UML中的动态模型用于示范类的对象在具体的环境中的行为。
下面我将详细说明。
第一节结构设计
一个设计良好的体系结构是开发一个可扩展、可改变的系统的基础,程序包所需要关心的是要么处理一个具体的功能区域,要么处理一个具体的技术区域。从技术逻辑中把应用程序逻辑(域类)区分开来是极其重要的,这是为了万一需要修改程序的某一部分而不会对另一部分产生影响:一个目标就是标识并设定包与包之间(例如“子系统”)的相互依赖的规则,并不在包之间创建双向的依赖(为了避免程序包集成的太过紧密),另一个目标是为了表示标准类库的需要。现在可用的应用程序库强调的主要还是在技术领域,比如用户界面,数据库或通信机制等等,但是,我们也同样盼望出现更多的具体的应用程序库。
本案例研究中的程序包或者说是子系统如下:
1、用户界面包(User-Interface)这些类都是基于Java AWT包这个Java中用于编写用户界面应用程序的一个标准的类库。这个程序包与商业对象包(Business Object)协作,商业对象包包含了实际上用于储存数据用的类,用户界面包调用商业对象中的方法来取得并向商业对象中插入数据。
2、商业对象包(Business Object)它包括来自分析模型,比如BorrowerInformation,Title,Item,Loan等等的讨论域类。该设计完全地定义了它们的操作并且添加了对于持久性的支持。商业对象包与数据库包合作,所有的商业对象类都必须从数据库包中的Persistent类继承而来。
3、数据库包(Database Package)数据库包给商业对象包中的另外一个类提供服务,以使它们能够持久的储存信息。在目前的版本,Persistent类将储存它的子类对象到文件系统中的文件中去。
4、实用程序包(Utility Package)实用程序包包含用于该系统中的另外一个包的服务,现在,该包中只有ObjId类,它用于引用遍及本系统的持久对象,包括用户界面,商业对象和数据库包。
这些程序包的内部设计见图4。
图4解释图书馆应用程序结构概图。这是一张类图,说明应用程序包以及它们之间的关系。数据库包提供了持久性,公用程序包提供了Object ID类,商业对象包包含了讨论域类,这点在图5中将详细列出。最后,基于标准Java AWT类库的UI包调用商业对象中的操作来向它们中间插入数据。
第二节详细设计
详细设计描述新的类--在用户界面和数据库包中的类,以及在本分析中描绘的商业对象类以外的人。本类的状态和动态图表使用的是与分析过程中一样的图表,但是它们被定义在更加详细和更高的技术层次,分析过程中的使用案例描述用于验证在设计阶段处理的使用案例,使用序列图表阐明在系统中,每个使用案例是如何在技术上实现的。
数据库包应用程序必须有持久储存对象,所以必须添加一个数据库层来提供这个服务,为了简单起见,我们把对象作为文件储存在磁盘上,关于存储器的细节就不需要被应用程序所知了,它调用通用操作,比如store()、update()、delete()和find()等等,这些都是一个调用Persistent的类的一部分,所有的类都需要继承Persistent(持久对象)。
持久性处理中的一个重要的因素就是ObjId类,它的对象用于引用任何系统中的持久对象(无论对象是在磁盘上还是已经被读入应用程序中了)。ObjId是Object Identity的简写,是一种熟知的技术,用于处理应用程序中的对象引用。通过使用对象标识,一个对象标识号就能被传递到Persistent.getObject ( )操作,然后该对象将从持久存储器中取回。通常,这要通过每个持久类中的getObject操作来完成,它还要执行必要的类型检查和转换。对象标识号还可以很容易地作为操作的参数被传递(例如,一个寻找具体对象的搜索窗口可以通过对象标识号传递它的结果到另外一个窗口)。
ObjId标识系统(用户界面、商业对象和数据库)中所有的包使用的一个常规类,因此它在设计阶段就被放进实用程序包中而不是数据库包中。Persistent类的当前实现还可以不够完善,它的最终目标是可以很容易的改变持久存储器的实现,目前的替代的办法是把对象出存在关系数据库或面向对象数据库中,也可以使用Java中的持久对象支持储存它们。
商业对象包在设计阶段中的商业对象包基于分析过程中相应的包——讨论域类。类以及它们的相互关系和行为没有变,但是类被描述的更加详细,包括了它们的相互关系和行为如何实现。
一些操作已经被翻译成好几个设计模型中的操作,一些还被改了名称,这都是很正常的,因为分析只是每个类的能力的描绘,而设计则是系统详细的描述,因此设计模型中的所有的操作都必须有定义好的特征和返回值,注意,下面给出了设计与分析的不同。
图5解释商业对象设计。这张图表充实了商业对象程序包的各种不同的类的设计。接口更加精确,选择了属性的数据类型。
系统的当前版本不必检查一本书是否及时归还,也不必处理预借书籍的订单,因此Loan和Reservation类的日期属性就没有实现。
杂志和书的处理过程是完全相同的,除了借期的不同,而且它还不用处理。在分析中,Magazine和Book Title子类已经被认为不必要的并且在Title类中只有一个类型属性指定该书名是否指出一本书或杂志。在以后的应用程序版本中,如果认为有必要的话,这两个简化都可以删除。
分析过程中的状态图表在设计阶段又被细化了,显示在工作系统中状态如何被表示以及被处理。Title类的设计状态图表如图6。其他对象可以通过调用addReservation ( )和removeReservation ( )操作来改变Title的状态,就像这张图表中所显示的那样。
图6解释设计Title的状态图
用户界面包用户界面包总是在其他包之前,在系统中,它给用户提供服务和信息,显然,这个包基于标准的Java AWT ( Abstract Window Toolkit )类。设计模型中的动态模型已经被分配到GUI包中,因为所有的与用户的交互作用都是通过用户界面开始的,此外,我们还选择序列图表来说明动态模型,本使用案例的设计模型的实现都是用细节描述的,包括类中的实际的操作。序列图表实际上是以一系列迭代的形式创建的。在实现(即编码)阶段更多的细节上的发掘会产生更进一步的迭代。图7表明Add Title的结果设计序列图表。
图7解释Add Title的序列图
我们还可以使用协作图表代替序列图表,象图8。
图8解释Add Title的协作图。第三节用户界面设计
在设计阶段,我们使用一个特定活动创建用户界面。
图书馆应用程序中的用户界面是基于本使用案例的,并且已经被分成下列部分,在主窗口上,它的每个部分都已经被给予一个单独的菜单栏:
1、功能本系统中的主要功能的窗口就是用来借书、还书以及与借书籍的登记工作等。
2、信息本系统中的查看信息的窗口就是用来收集书名和借书者的信息。
3、维护维护本系统的窗口用来添加、更新和删除书名、借书者以及书籍。
图9 是一个用户界面包中的类图的例子。
图9解释功能类图模型。
一般情况下,每个窗口提供一个系统中的服务并且映射到一个使用案例(即使并不是所有的用户界面都必须从一个使用案例中映射而来),创建一个成功的用户界面超出本文讨论的范围,读者朋友请参阅文后提供的代码。我以后还会专门辑文探讨这个问题。
实现
程序设计在构造或实现阶段就开始了,应用程序的要求规定本系统能够运行于各种不同的处理器和操作系统,因此Java语言是实现本系统的最好的选择。Java可以很容易的映射逻辑类到代码组件,因为一个类有到Java代码文件的一对一的映射。图10说明了在本例中的设计模型的组件图表包含一个逻辑视图中的类到组件视图的组件的简单映射。每个组件包含一个逻辑视图中的类的描述的链接,这样就使在不同的视图之间定位变得很容易(即使,象在本例中,它只是简单的使用了文件名)。组件之间的依赖在组件图表中并没有表示出来(除了商业对象包),因为可以从逻辑视图中的类图衍生出它们之间的依赖。
图10
为了编码,要从设计模型中的下列图表中取得规范:
类规范:每个类的规范,用于详细地说明必要的属性和操作。
类图:它所要介绍的类的类图,说明了它的静态结构和与其他类的相互关系。
状态图:类的状态图,说明了可能的状态以及需要被处理的过渡期(以及触发该过渡期的操作)。
类的对象中包含的动态图(序列图、协作图以及活动图):说明类的一个具体的方法的实现的图表或者是说明其他对象是如何使用类的对象的图表。
使用案例图表以及规范:等开发者需要知道关于系统使用情况时说明系统被使用的结果(当开发者觉得被整个系统的细节问题所搞糊涂的时候)。
显然,设计阶段的不足将在编码阶段暴露出来,我们需要找出哪些操作需要更新、哪些操作需要修改,这就意味着开发者将不得不改变设计模型。在所有个项目开发中都会遇到这种事情,重要的是,我们要使设计模型和代码同步,这样设计模型就能被称为系统最后的所需要的设计。
考虑下面这些要点:
Java程序包规范是规定这个类所属的组件或逻辑视图的等价代码。
私有属性符合模型中规定属性的。并且,Java方法符合模型中的操作。
ObjId类(对象标识符)被调用来实现关联,这意味着关联通常被和该类一起保存(因为ObjId类是持久的类)。