UML基础: 统一建模语言简介



developerWorks 中国  >  Rational  >

UML基础: 统一建模语言简介

developerWorks
文档选项
<script language="JavaScript" type="text/javascript"> </script>
将打印机的版面设置成横向打印模式

打印本页

<script language="JavaScript" type="text/javascript"> </script>
将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

Donald Bell, IBM 全球服务, IBM

2004 年 2 月 01 日

回顾20世纪晚期--准确地说是1997年,OMG组织(Object Management Group对象管理组织)发布了统一建模语言(Unified Modeling Language,UML)。UML的目标之一就是为开发团队提供标准通用的设计语言来开发和构建计算机应用。UML提出了一套IT专业人员期待多年的统一的标准建模符号。通过使用UML,这些人员能够阅读和交流系统架构和设计规划--就像建筑工人多年来所使用的建筑设计图一样。

到了21世纪--准确地说是2003年,UML已经获得了业界的认同。在我所见过的专业人员的简历中,75%都声称具备UML的知识。然而,在同绝大多数求职人员面谈之后,可以明显地看出他们并不真正了解UML。通常地,他们将UML用作一个术语,或对UML一知半解。大家对UML缺乏理解的这种状况,促进我撰写这篇关于UML 1.4的快速入门文章。当阅读完本文时,您还不具备足够的知识可以在简历上声称自己掌握了UML,但是您已具有了进一步钻研该语言的良好起点。

参考 UML 基础系列的其他文章和教程

RSS 订阅 UML 相关文章和教程的 RSS 提要

一些背景知识


正如前面曾提到过的,UML的本意是要成为一种标准的统一语言,使得IT专业人员能够进行计算机应用程序的建模。UML的主要创始人是Jim Rumbaugh、Ivar Jacobson和Grady Booch,他们最初都有自己的建模方法(OMT、OOSE和Booch),彼此之间存在着竞争。最终,他们联合起来创造了一种开放的标准。(听起来是不是很熟悉?这个现象类似J2EE、SOAP和Linux的诞生。)UML成为"标准"建模语言的原因之一在于,它与程序设计语言无关。(IBM Rational的UML建模工具被广泛应用于J2EE和.NET开发。)而且,UML符号集只是一种语言而不是一种方法学。这点很重要,因为语言与方法学不同,它可以在不做任何更改的情况下很容易地适应任何公司的业务运作方式。

既然UML不是一种方法学,它就不需要任何正式的工作产品(即IBM Rational Unified Process?术语中所定义的"工件")。而且它还提供了多种类型的模型描述图(diagram),当在某种给定的方法学中使用这些图时,它使得开发中的应用程序的更易理解。UML的内涵远不只是这些模型描述图,但是对于入门来说,这些图对这门语言及其用法背后的基本原理提供了很好的介绍。通过把标准的UML图放进您的工作产品中,精通UML的人员就更加容易加入您的项目并迅速进入角色。最常用的UML图包括:用例图、类图、序列图、状态图、活动图、组件图和部署图。

深入讨论每类图的细节问题已超出了这篇入门文章的范围。因此,下面仅给出了每类图的简要说明,更详细的信息将在以后的文章中探讨。





回页首


用例图


用例图描述了系统提供的一个功能单元。用例图的主要目的是帮助开发团队以一种可视化的方式理解系统的功能需求,包括基于基本流程的"角色"(actors,也就是与系统交互的其他实体)关系,以及系统内用例之间的关系。用例图一般表示出用例的组织关系--要么是整个系统的全部用例,要么是完成具有功能(例如,所有安全管理相关的用例)的一组用例。要在用例图上显示某个用例,可绘制一个椭圆,然后将用例的名称放在椭圆的中心或椭圆下面的中间位置。要在用例图上绘制一个角色(表示一个系统用户),可绘制一个人形符号。角色和用例之间的关系使用简单的线段来描述,如图1所示。


示例用例图

图1:示例用例图

图字(从上到下):CD销售系统;查看乐队CD的销售统计;乐队经理;查看Billboard 200排行榜报告;唱片经理;查看特定CD的销售统计;检索最新的Billboard 200排行榜报告;排行榜报告服务

用例图通常用于表达系统或者系统范畴的高级功能。如图1所示,可以很容易看出该系统所提供的功能。这个系统允许乐队经理查看乐队CD的销售统计报告以及Billboard 200排行榜报告。它也允许唱片经理查看特定CD的销售统计报告和这些CD在Billboard 200排行榜的报告。这个图还告诉我们,系统将通过一个名为"排行榜报告服务"的外部系统提供Billboard排行榜报告。

此外,在用例图中,没有列出的用例表明了该系统不能完成的功能。例如,它不能提供给乐队经理收听Billboard 200上不同专辑中的歌曲的途径 -- 也就是说,系统没有引用一个叫做"收听Billboard 200上的歌曲"的用例。这种缺少不是一件小事。在用例图中提供清楚的、简要的用例描述,项目赞助商就很容易看出系统是否提供了必须的功能。





回页首


类图


类图表示不同的实体(人、事物和数据)如何彼此相关;换句话说,它显示了系统的静态结构。类图可用于表示逻辑类,逻辑类通常就是业务人员所谈及的事物种类--摇滚乐队、CD、广播剧;或者贷款、住房抵押、汽车信贷以及利率。类图还可用于表示实现类,实现类就是程序员处理的实体。实现类图或许会与逻辑类图显示一些相同的类。然而,实现类图不会使用相同的属性来描述,因为它很可能具有对诸如Vector和HashMap这种事物的引用。

类在类图上使用包含三个部分的矩形来描述,如图2所示。最上面的部分显示类的名称,中间部分包含类的属性,最下面的部分包含类的操作(或者说"方法")。


类图中的示例类对象

图2:类图中的示例类对象

根据我的经验,几乎每个开发人员都知道这个类图是什么,但是我发现大多数程序员都不能正确地描述类的关系。对于像图3这样的类图,您应该使用带有顶点指向父类的箭头的线段来绘制继承关系1,并且箭头应该是一个完全的三角形。如果两个类都彼此知道对方,则应该使用实线来表示关联关系;如果只有其中一个类知道该关联关系,则使用开箭头表示。


一个完整的类图

图3:一个完整的类图,包括了图2所示的类对象

在图3中,我们同时看到了继承关系和两个关联关系。CDSalesReport类继承自Report类。一个CDSalesReport类与一个CD类关联,但是CD类并不知道关于CDSalesReport类的任何信息。CD类和Band类都彼此知道对方,两个类彼此都可以与一个或者多个对方类相关联。

一个类图可以整合其他许多概念,这将在本系列文章的后续文章中介绍。





回页首


序列图


序列图显示具体用例(或者是用例的一部分)的详细流程。它几乎是自描述的,并且显示了流程中中不同对象之间的调用关系,同时还可以很详细地显示对不同对象的不同调用。

序列图有两个维度:垂直维度以发生的时间顺序显示消息/调用的序列;水平维度显示消息被发送到的对象实例。

序列图的绘制非常简单。横跨图的顶部,每个框(参见图4)表示每个类的实例(对象)。在框中,类实例名称和类名称之间用空格/冒号/空格来分隔,例如,myReportGenerator : ReportGenerator。如果某个类实例向另一个类实例发送一条消息,则绘制一条具有指向接收类实例的开箭头的连线,并把消息/方法的名称放在连线上面。对于某些特别重要的消息,您可以绘制一条具有指向发起类实例的开箭头的虚线,将返回值标注在虚线上。就我而言,我总喜欢绘制出包括返回值的虚线,这些额外的信息可以使得序列图更易于阅读。

阅读序列图也非常简单。从左上角启动序列的"驱动"类实例开始,然后顺着每条消息往下阅读。记住:虽然图4所示的例子序列图显示了每条被发送消息的返回消息,但这只是可选的。


一个示例序列图

图4:一个示例序列图

通过阅读图4中的示例序列图,您可以明白如何创建一个CD销售报告(CD Sales Report)。其中的aServlet对象表示驱动类实例。aServlet向名为gen的ReportGenerator类实例发送一条消息。该消息被标为generateCDSalesReport,表示ReportGenerator对象实现了这个消息处理程序。进一步理解可发现,generateCDSalesReport消息标签在括号中包括了一个cdId,表明aServlet随该消息传递一个名为cdId的参数。当gen实例接收到一条generateCDSalesReport消息时,它会接着调用CDSalesReport类,并返回一个aCDReport的实例。然后gen实例对返回的aCDReport实例进行调用,在每次消息调用时向它传递参数。在该序列的结尾,gen实例向它的调用者aServlet返回一个aCDReport。

请注意:图4中的序列图相对于典型的序列图来说太详细了。然而,我认为它才是足够易于理解的,并且它显示了如何表示嵌套的调用。对于初级开发人员来说,有时把一个序列分解到这种详细程度是很有必要的,这有助于他们理解相关的内容。





回页首


状态图


状态图表示某个类所处的不同状态和该类的状态转换信息。有人可能会争论说每个类都有状态,但不是每个类都应该有一个状态图。只对"感兴趣的"状态的类(也就是说,在系统活动期间具有三个或更多潜在状态的类)才进行状态图描述。

如图5所示,状态图的符号集包括5个基本元素:初始起点,它使用实心圆来绘制;状态之间的转换,它使用具有开箭头的线段来绘制;状态,它使用圆角矩形来绘制;判断点,它使用空心圆来绘制;以及一个或者多个终止点,它们使用内部包含实心圆的圆来绘制。要绘制状态图,首先绘制起点和一条指向该类的初始状态的转换线段。状态本身可以在图上的任意位置绘制,然后只需使用状态转换线条将它们连接起来。


显示类通过某个功能系统的各种状态的状态图

图5:显示类通过某个功能系统的各种状态的状态图

图5中的状态图显示了它们可以表达的一些潜在信息。例如,从中可以看出贷款处理系统最初处于Loan Application状态。当批准前(pre-approval)过程完成时,根据该过程的结果,或者转到Loan Pre-approved状态,或者转到Loan Rejected状态。这个判断(它是在转换过程期间做出的)使用一个判断点来表示--即转换线条间的空心圆。通过该状态图可知,如果没有经过Loan Closing状态,贷款不可能从Loan Pre-Approved状态进入Loan in Maintenance状态。而且,所有贷款都将结束于Loan Rejected或者Loan in Maintenance状态。





回页首


活动图


活动图表示在处理某个活动时,两个或者更多类对象之间的过程控制流。活动图可用于在业务单元的级别上对更高级别的业务过程进行建模,或者对低级别的内部类操作进行建模。根据我的经验,活动图最适合用于对较高级别的过程建模,比如公司当前在如何运作业务,或者业务如何运作等。这是因为与序列图相比,活动图在表示上"不够技术性的",但有业务头脑的人们往往能够更快速地理解它们。

活动图的符号集与状态图中使用的符号集类似。像状态图一样,活动图也从一个连接到初始活动的实心圆开始。活动是通过一个圆角矩形(活动的名称包含在其内)来表示的。活动可以通过转换线段连接到其他活动,或者连接到判断点,这些判断点连接到由判断点的条件所保护的不同活动。结束过程的活动连接到一个终止点(就像在状态图中一样)。作为一种选择,活动可以分组为泳道(swimlane),泳道用于表示实际执行活动的对象,如图6所示。


活动图

图6:活动图,具有两个泳道,表示两个对象的活动控制:乐队经理,以及报告工具

图字(沿箭头方向):乐队经理;报告工具;选择"查看乐队的销售报告";检索该乐队经理所管理的乐队;显示报告条件选择屏幕;选择要查看其销售报告的乐队;从销售数据库检索销售数据;显示销售报告。

该活动图中有两个泳道,因为有两个对象控制着各自的活动:乐队经理和报告工具。整个过程首先从乐队经理选择查看他的乐队销售报告开始。然后报告工具检索并显示他管理的所有乐队,并要求他从中选择一个乐队。在乐队经理选择一个乐队之后,报告工具就检索销售信息并显示销售报告。该活动图表明,显示报告是整个过程中的最后一步。





回页首


组件图


组件图提供系统的物理视图。它的用途是显示系统中的软件对其他软件组件(例如,库函数)的依赖关系。组件图可以在一个非常高的层次上显示,从而仅显示粗粒度的组件,也可以在组件包层次2上显示。

组件图的建模最适合通过例子来描述。图7显示了4个组件:Reporting Tool、Billboard Service、Servlet 2.2 API和JDBC API。从Reporting Tool组件指向Billboard Service、Servlet 2.2 API和JDBC API组件的带箭头的线段,表示Reporting Tool依赖于那三个组件。


组件图显示了系统中各种软件组件的依赖关系

图7:组件图显示了系统中各种软件组件的依赖关系





回页首


部署图


部署图表示该软件系统如何部署到硬件环境中。它的用途是显示该系统不同的组件将在何处物理地运行,以及它们将如何彼此通信。因为部署图是对物理运行情况进行建模,系统的生产人员就可以很好地利用这种图。

部署图中的符号包括组件图中所使用的符号元素,另外还增加了几个符号,包括节点的概念。一个节点可以代表一台物理机器,或代表一个虚拟机器节点(例如,一个大型机节点)。要对节点进行建模,只需绘制一个三维立方体,节点的名称位于立方体的顶部。所使用的命名约定与序列图中相同:[实例名称] : [实例类型](例如,"w3reporting.myco.com : Application Server")。


部署图

图8:部署图。由于Reporting Tool组件绘制在IBM WebSphere内部,后者又绘制在节点w3.reporting.myco.com内部,因而我们知道,用户将通过运行在本地机器上的浏览器来访问Reporting Tool,浏览器通过公司intranet上的HTTP协议与Reporting Tool建立连接。

图8中的部署图表明,用户使用运行在本地机器上的浏览器访问Reporting Tool,并通过公司intranet上的HTTP协议连接到Reporting Tool组件。这个工具实际运行在名为w3reporting.myco.com的Application Server上。这个图还表明Reporting Tool组件绘制在IBM WebSphere内部,后者又绘制在w3.reporting.myco.com节点内部。Reporting Tool使用Java语言通过IBM DB2数据库的JDBC接口连接到它的报告数据库上,然后该接口又使用本地DB2通信方式,与运行在名为db1.myco.com的服务器上实际的DB2数据库通信。除了与报告数据库通信外,Report Tool组件还通过HTTPS上的SOAP与Billboard Service进行通信。





回页首


结束语


尽管本文仅提供了对统一建模语言UML的简要介绍,但还是鼓励大家把从这里学到的基本信息应用到自己的项目中,同时更深入地钻研UML。已经有多种软件工具可以帮助您把UML图集成到软件开发过程中,不过即使没有自动化的工具,您也可以使用白板上的标记或者纸和笔来手工绘制UML图,仍然会获益匪浅。




统一建模语言(UML) 版本 2.0

对模型驱动开发的支持

developerWorks
文档选项
<script language="JavaScript" type="text/javascript"> </script>
将打印机的版面设置成横向打印模式

打印本页

<script language="JavaScript" type="text/javascript"> </script>
将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

Bran Selic (bselic@ca.ibm.com), 杰出工程师, IBM 

2005 年 4 月 29 日

所谓的“模型驱动”开发(MDD)方式,已经显示出了它们从根本性上提高软件质量和开发生产力方面的潜力。与传统的方法相比,这种方式是基于较高层次上的抽象和更好的自动化利用的。由于建模语言对MDD的成功具有关键性的作用,所以最近完成了对基于工业标准的统一建模语言(UML)的主要修订。随着一些重要的新的建模能力添加到其中――比如更精确地获得软件架构的能力――这次修订的主要特性使得语言定义更加精确,从而达到了更高层次的自动化。这篇文章解释了这一特性是如何实现的,并且描述了 UML 2.0 的其他亮点。

简介

参考 UML 基础系列的其他文章和教程

RSS 订阅 UML 相关文章和教程的 RSS 提要

可以看到1990年的早期版本已经对对象模式和相关技术有着浓厚的兴趣。基于这个模式的新的编程语言(比如Smalltalk, Eiffel, C++, 和Java)已经被设计并投入使用。伴随着这些语言出现的还有令人惊叹和难以理解的面向对象(object-oriented(OO))软件设计方法和建模符号。因而在彻底的纵览了有关OO分析和设计方法后(包含800页以上),Graham列举了50种以上的有相当大影响力的方法(Graham01)。考虑到对象模式包含了基本概念的相对较小的子集(包括封装、继承和多态),很明显,在这些方法中存在非常多的重叠和概念上的结合--大多数情况下是由于符号性的和其他并不重要的差异而使其变得很模糊不好理解。这样就导致了令人难以理解以及不必要的市场分歧—反过来也阻碍了有实用价值的新模式的采用。软件开发者很难在这些相互矛盾的语言,工具,方法和供应商中做出选择。

由于这个原因,当Rational 软件随后提出了统一建模语言(UML)的初始版时--在Grady Booch,Ivar Jacobson和Jim Rumbaugh的领导下--得到了快速和积极的反响。其目的并不是为了提出任何新的内容,而是—--通过高级领域思想领导者们的协作—--把各种各样的OO方法的最好特性添加到一个单独的和与供应商无关的模型化语言和注释中。正因为如此,UML很快地成为了一个广泛的实践标准。随着1996年对象管理组织(Object Management Group)对它的采用,UML成为了一个广泛被接受的工业标准[OMG03a] [OMG04] [RJB05]。

从那以后,UML:

  • 被绝大多数的模型工具开发商所采用和支持
  • 成为全世界大学和各种各样的专业培训项目中计算机科学和工程课程中必不可少的一部分。
  • 开始被学术和其他研究者所使用,并作为很便利的公共语言。

在处理复杂软件时,UML同样能够帮助增强对模建模价值的普遍的认识。尽管这种非常实用的技术几乎和软件本身一样历史悠久—――像很早以前的例子那样它们都带有数据流图(flowcharts)和有限状态机—――大多数开发人员慢慢地才接受它,如同接受其他工具一样,而不是像接受一个有用的小工具那样迅速。客观的说,这种对新事物的态度仍旧是一个占有主导性地位的,这就是为什么模型驱动方法在这一领域中受到很大的阻力的原因。

之所以会存在上述情形是有一些原因的(当然也有些不是那么有根据的原因,比如:通常人们并不相信创新)。主要原因是软件模式经常会导致不可预知的严重性错误:我们都清楚,任何一个模式的实际价值与它的正确性直接成比例。如果一个模式不能把它所表示的软件系统向你准确的表现出来,那么还不如不用模式,因为它可能会导致错误的结论。那么提高软件价值的关键在于缩小这些模式和它们所模式化的系统之间的差距。然而,正如这篇文章后面所论述的,在软件中减小这种差距比在其他任何的工程学科中要容易的多。

某些错误的软件模式归咎于当前编程语言的过多的细节和敏感的本质。一些较小的失误和几乎不被发觉的编码错误――比如指针偏差或者变量未初始化――可能会带来严重的后果。举一个实例来说,在一个有相关文档记录的案例中记载,由于一个嵌套的switch语句中的某个case中少了一个break,结果导致美国的大部分地区失去了长途电话服务,以致带来了巨大的经济损失【lee92】。如果这些看似微小的细节会带来如此可怕的后果,那么我们又如何相信模式的正确性(因为从定义上来看,模式应该用来隐藏或是去掉这些细节)?





回页首


模型驱动开发

解决这一难题的方法是通过一个或多个自动的模型转换器将一个模型与它相应的软件实现从形式上连接起来。也许这方面最好和最成功的例子就是编译器,它能够将一个高级语言程序解释成一个与之相当的机器语言的执行程序。这种情况下,模式就是这个高级语言程序,它就像所有有用的模式那样,隐藏了潜在的计算技术特性上的相关细节(比如内存字符大小,累加器的个数,索引寄存器,ALU算术逻辑单元类型等等)。

有趣的是,几乎没有任何其他的工程媒介能够为一个模型和它相应的工程工具提供如此紧密的连接。这是因为你所模式化的工具是软件而不是硬件。任何一种物理工具的模式(比如:一辆汽车,一座建筑物,一架桥等等)不可避免地包含了一些步骤,它会将物理特性抽象成一个相应的模型(就像数学或几何模型一样)。同样,使用物理原料实现一个抽象的模型包含了从抽象到具体的非正式转换。这一非正式步骤的本质会导致一些错误,正如上面所提到的,会导致模型效率低下或是甚至达不到预期目的。然而,对软件来说,原则上,这种转换可以从各个角度的形式上的执行。

抽象性自动化抽象性与自动化操作强有力的结合后,所产生的潜能已经导致新的建模技术和相关发展方法的出现,正如所提及的模型驱动开发MDD) [Brown04] [Booch04]。MDD的定义特征是,此模型已经成为软件设计的主要工具,它把许多注意力从相关的程序代码上转移开。它们为不同的自动化和半自动化的方法提供服务,这种方法源于代码和相关的模型。与传统的编码相比较,目前在MDD中使用自动化操作的程度不同于从简单框架代码到完全自动的产生代码。很明显地,自动化程度越高,模型越精确,MDD的优越性就更突出。

软件发展的模型驱动方法不是一种独特地新式方法,在过去就已经被使用并获得了不同程度地成功。他们之所以愈来愈受重视的原因是,其支持性技术已经愈来愈成熟,成熟点在于比起过去的情况来看在实践上更加自动化。这不仅仅在效率方面,而且在可测量性方面,同样这种工具所具有的能力是与继承性工具和方法相结合的。这种成熟技术所反应的MDD标准的出现,使得使用相关的工具时更加便利舒适,给使用者带来显而易见的好处。其标准之一是统一建模语言的修订版。





回页首


修订UML 1后的基本原理

UML 2.0是UML标准最主要的修订本,以下的是一系列次要的修订本[OMG04] [RJB05]。为什么修订UML是必要的呢?

修订这种语言最初的动机源于更好的支持MDD工具和方法的要求。在过去的十年中,许多供应商已经发展了基于UML的工具,值得注意的是这些工具所支持的自动化标准比传统的CASE(计算机辅助软件工程)工具标准更高。

为了支持这些更高标准的自动化形式,需要用一个比原始标准规定更加准确的方式来定义UML。(从与时俱进的角度来看,最初地原始UML标准设计是作为一种辅助工具来服务的,即为非正式的捕捉和设计意图的传达提供服务)。不幸地是,这些定义因商家的不同而不同,它的危险性导致了一些分歧的产生,而这些分歧是应该从旧版本的标准中被排除掉的。一个新版本的标准可以修正它。

另外,在近十年的使用UML的实践经验之后――同样在此期间重要的新技术也随之产生了(例如基于web的应用软件和基于服务的体系结构),新的建模性能得到了肯定。事实上,当这些新技术通过现有UML概念的适当结合表现出来时,将它们作为优秀的内嵌语言特性引入是有明显益处的。

最终,在同样漫长的时间内,业界已经学会了许多有关如何使用的适当方法来构建和定义模型语言。例如,目前将要出现的外部模型和模型转换的理论,它强制性要求一个模型语言如何来定义。由于与当前程序设计语言理论相比,我们一直缺乏一个统一的、系统的建模语言设计理论,因此我们需要把这些理论以及类似的发展合并在UML中,这样才能确保其效用和持久性。





回页首


UML 2.0 特性的亮点

UML 2.0的新改进可分为以下五个主要方面,按重要性顺序列出:

  • 在语言定义方面精确程度有了相当的提高: 这就是支持自动化高标准需要的结果,此标准是MDD所必须的。自动化意味着模型(以及后来的模型语言)的不明确和不精密的消除,所以计算机程序能转换并熟练地操纵模型。
  • 一个改良的语言组织: 其特性是由模块化决定的,模块化的特点在于它不仅使得语言更加容易的被新用户所采用,而且促进了工具之间的相互作用。
  • 重点改进大规模的软件系统模型性能: 一些流行的应用软件表现出将现有的独立应用程序集成到更加复杂的系统中去。这是一种趋势,它将可能会继续导致更加复杂的系统。为了支持这种趋势,将更加灵活和新的分等级的性能添加到语言中去,用以支持软件模型在任意复杂的级别中使用。
  • 对特定领域的改进的支持: 使用 UML 的实践经验证明了其所谓的“扩展”机制的价值。这些机制被统一化,精炼化后,使得基础语言更加简化,更加准确精炼。
  • 全面的合并,合理化,清晰化各种不同的模型概念: 从而导致一种单一化,更加统一化语言的产生。它包含了合并和――在一些案例中――消除多余的概念,精练各种各样的定义,添加文字性的解释和例子。

现在我们来更详细地研究一下上述的每个方面。

精确程度

大多数的早期软件建模语言被非正式地定义,并很少注重它的精确性。时常,建模概念被解释成使用不严密的自然语言。由于大多数的建模语言在文件中或在Martin Fowler所提及的设计草图[Fowler04]中所使用,在那个时期,此模型概念得到了充分信任。这种思想传达了一种设计的本质特性,而把细节留给实现阶段去处理。

然而,由于模型在这种语言中很可能――并且通常是――被不同的商家解释成不同的含义,因此经常导致概念混淆。此外,除非模型解释的问题事先已被明确地讨论过,否则像这样的分歧还不能被人所发觉,而只是在发展的较后阶段才能被发现(即当问题的结果已明显显现时候)。

为了把不明确的概念减少到最少――并和多数现代的其它模型语言形成对比―― 第一个标准化的UML定义是用元模型来指定的。这是一个定义每一种UML 建模概念特性和这些特性与其他相关概念直接的关系的模型。使用UML的基本子集来定义这个元模型,并且通过一系列在对象约束语言中(OCL)正式的强制进行补充。

注释:: 这种UML子集,主要是由定义在UML上的类图上的概念所组成的,它被称为元对象工具(MOF)。选择这个子集后,它可以用来定义其它的建模语言。

这种结合所描述的是,UML抽象语法的一种正式的规范。正式的规范。之所以被称作是正式的,其原因是它与实际的符号或用于描绘模型的具体语法具体语法(也就是说,文本和图表)无关。换句话说就是,它所定义的规则集可以用来确定一个特定的模型是否已经很好的成形很好的成形。例如,这种规则将允许我们去测定通过一个状态机转换来连接两个UML类是不正确的。

然而,在这个初始的UML 元模型中所使用的精确程度证实,在MDD(例如在[Stevens02]中讨论所见到的)后,对整个潜能的支持是远远不够的。特别是,UML建模概念的语义(或含义)的规范,对这些作为自动代码生成或正式确认的基于MDD的活动仍旧是不适当的。

因此,值得注意的是在UML 2.0中定义所使用的精确程度已经增强了。它是通过以下方法完成的:

  • 一种元模型架构的主要重建: UML 2.0架构是由一组低层次的建模概念和模式所组成的,它们在大多数的案例中要么过于初级,要么太抽象,以至于不能直接地在建模软件应用程序中使用。然而,它们相对的简单性使得它们在其语义和形成的规则上更加精确。这些优化后的概念,以不同的方法结合产生了更加复杂的用户级别的建模概念。例如,在UML 1中,在所有权角度上(即,元素包含另外一些元素),命名空间的概念(也叫唯一命名的元素集合)与分类器的概念(元素是能根据它们的属性进行分类的)上,都与单个的复杂语义概念绑定在一起。(注意这同样意味着如果没有包含另外两个而使用其中的任意一个的话,那是不可能的。)在新的UML 2.0架构中,这些概念被分离开,并且它们的语法和语义也被单独的定义。
  • 可扩展的和更加精确的语义描述:UML 1模型概念语义的定义在许多方法都存在问题。它所描述的层次有些地方具有某些广泛的和详细的描述(例如,状态机),但是非常不平均,而其它的一些地方几乎没有解释。UML 2.0规范主要强调了语义,尤其是在基本行为动态的关键领域中(如下所述)。对于一个更加详细的UML 2.0语义的讨论,请参考资源部分中的[Selic04]。
  • 一种清晰定义的动态语义框架: UML 2.0规范澄清了一些在老版本中的严重语义缺陷。图一描述了这个框架,至于更多的细节在资源中有所描述。[Selic04]。此外,下面的问题将通过这个框架详细的描述出来:
    • 在运行期间的链接和实例的结构化语义
    • 结构和行为之间的关联
    • 语义的基础或因果关系模型通过所有当前在UML中的高级行为形式(即状态机,活动,交互)所共享。这同样也确保了那些通过不同的形式表达行为的对象可以相互的交互。


图 1. UML2.0语义框架
UML2.0语义框架

新的语言架构

UML 2.0 在精确度方面的提升所造成的最直接的结果之一,就是即使不算它所新增的建模能力部分,这种语言的定义也变得更大了。特别是对于最初的UML,曾被批评过于庞大(以至于学习和使用起来太麻烦),对于现在更加庞大的定义,这点通常又将被关注。

这样的批评典型地忽略了一个事实,那就是UML原本就是用来表述一些现在最复杂的软件问题,这样的问题当然需要功能充分强大的工具。(成功的科技——如同汽车和电子学,从来没有变得简单;对机器持续不断的要求是人类的本性之一,这就造成了最终越来越复杂的工具。例如,没有人会企图用基本的手工建造现代的摩天大厦。)

不过,由于有了这些顾虑,UML2.0 在某种程度上进行了模块化,允许有选择性的使用一些语言模块,以便解决语言复杂度的问题。这种结构的通常形式如图2所示。一些像类和关联这样的共享概念组成了它的基础部分,顶部是垂直的子语言或语言单元的一个集合,集合中的每个单元都很适合用来对某个具体的方面进行建模(Table 1)。这些垂直的语言单元一般都是相互独立的,因此你可以单独地使用它们。(注意:这在UML1中是不行的,在UML1中,活动的形式完全是基于状态机的形式。)


图 2. UML 2.0的语言架构
UML 2.0的语言架构

此外,垂直语言单元按级别组织成三层,通过在那些可用的层上增加建模能力可以形成相互连续的更高层。这就对模块性提供了更多的空间,即使对一个已给定的语言单元,你也有可能只使用某些特定的子集。

这样的架构意味着,你可以学习和使用UML那些最适合你的部分。你不再需要为了有效地使用UML去熟悉它所有的内容,就如同你不必为了说好英语而去学习英语里所有的内容一样,从这点来说,它可能比学英语更简单。随着你经验的增长,如果有必要你可以逐渐引入更强大的建模概念。

表1 UML 2.0的语言单元

语言单元 目的
动作(基础) 细粒度动作的建模
活动数据和控制流行为建模
(基础) 基本结构的建模
组件组件技术的复杂结构建模
部署部署建模
通用行为(基础)公共行为语义基础和时间建模
信息流抽象数据流建模
交互内部对象行为建模
建模模型组织
Profiles语言定制化
状态机事件驱动行为建模
结构复杂的结构建模
模板模式建模
用例非正式的行为需求建模

作为相同架构下重组的一部分,在UML2.0中,语言的定义和结构的灵活性被显著地简化了。在UML1中,规范性的基本单元是由元模型的包定义的,包含了差不多成百个可能的组合。(事实上,因为UML 1 为一个特定的适应性给出了规范化的但又不完全的灵活定义 ,也就是说这些性能可以有很多种不同的组合)这就意味着,几乎不可能找到两个或更多的建模工具能相互之间进行模型交换,因为每一种工具可能只支持包的一种不同的组合。

在UML 2.0中,只定义了三个规范性层次,那些对应于分级语言单元的层已经在0层中就被提及并描述了。它们是这样被定义的,层(n)的模型服从于任何比它更高的层(如n+1)所定义的模型。换句话说,一个符合给定层规范的工具可以从那些符合任一相同或低于它所在层规范的工具中导入模型――在没有丢失信息的情况下。

注释: 形式上,UML 2 也定义了第四层(层 0),但这只是一个内部层,主要用来供工具的实现者使用。

四种规范标准类型的定义

  • 抽象句法规范标准
  • 混合句法规范标准(也就是UML符号)
  • 抽象句法和混合句法规范标准
  • 抽象句法和混合句法规范,和图之间相互交换的标准

这就意味着最多有12种不同规范标准组合,并且它们之间有着清晰的从属关系。(比如,抽象和具体的语法标准与仅仅是具体的或仅仅是抽象的语法标准保持一致)。从而使得在UML2.0中不同厂商的工具之间的模型交换成为可能,而不仅仅只停留在理论上。

大规模系统建模能力

在UML2.0中,新增的特性相对来说不是很多。这是特意为了避免已经声名狼藉的“次要系统”效应[Brooks95],因为一门语言没有必要由于非常多变的分用户群的提出新的需求而过度的膨胀。实际上,新的建模能力的主要实质是对已存在的特征进行简单地扩充,以便用于大规模的软件系统的建模。

此外,这些扩展都是通过使用相同的基本方法达到的:即在不同的抽象层面上递归地应用那些相同的基本概念集。这就意味着,你可以把一个给定类型的模型元素合并到单元里,依次类推,你可以用这种方式在下个抽象层面上进行合并,并把这些合并后的单元作为一个模块进行使用。这跟编程语言中的过程类似,它能根据你想要的深度进行嵌套的调用。

特别是,以下建模能力通过这样的方式被扩展了:

  • 复杂结构
  • 活动
  • 交互
  • 状态机

上述的前三项占到了UML2.0的新特性中的90%以上

复杂结构

这一系列特征的依据来自于对不同架构描述语言(如UML-RT [SR98], Acme [GMW97], 和 SDL [ITU02])长期使用的经验。这些语言的特点在于,它们通过相对简单的像图这样的概念被描述:基本的结构性节点,也就是所谓的部件,他们可以有一个或多个端口,它们之间可以由被称作连接器的通信通道进行连接(如图3所示)。这些集合体可以被封装成更高层的单元,依次类推,这些新封装成的单元也可以有自己的端口以便于与其他更高层的单元合并成更高层的单元


图 3. 复杂结构建模概念
复杂结构建模概念

从某种程度上来说,这些概念在UML 1 中对于协作的定义里可以找到,只可惜它们不能用于递归。为了允许递归,协作结构被嵌套到类的规范中。这就是说这个类的所有实例都将有一个由类定义的内部结构。例如,在图3中,部件(part)/a:A 和/b:B 都被嵌套在部件(part)/c:C中,从而表现了这个复杂结构类C的一个实例。而这个类的其他实例都会有相同的结构模式(包括所有的端口,部件以及连接器)

这就证明了,你可以通过这三个简单的概念,以及它们的递归应用,对任何复杂的软件架构建模。

活动

在UML中,活动被用来对不同种类的流程建模:信号流数据流,也有算法流过程流。不用说,对于众多的领域及应用而言,基于流的描述是最自然的表现方式了。对于业务过程建模者和那些想主要通过信号处理器浏览整个系统的系统工程师,这种形式更是特别受欢迎。不幸的是,在UML 1中,行为建模在流的类型方面有大量严格的限制,这些限制被提出了异议。这其中的很多限制都是由于在基本的状态机的顶部行为被覆盖了,所以,它们受限于状态机的语义。

UML 2.0中用一个消除了这些限制的更泛化的语义基础替代了状态机的底层。此外,这些语义基础也从很多行业标准和业务过程形式中得到灵感,其中包括BPEL4WS [BPEL03]——在基础的形式上增加了一系列非常丰富并且非常精确的建模特征。这包括以下一些能力:

  • 中断的活动流
  • 复杂形式的并发控制
  • 多样的缓冲配置

这就形成了一个丰富的建模工具集,能广泛地表示不同的流类型。

由于其复杂的结构,你可以对活动递归地进行分组并对流进行连接以形成更高的层,这种层次清晰地定义了输入和输出。你可以一次把这些活动与其他活动合并形成更复杂的活动,一直到最高的系统层。

交互

在UML1 中,交互性是由协作图中序列消息的注释或单独的序列图来表现的。不幸的是,这样导致失去了两个基本能力:

  1. 对序列进行重用的能力,也就是可以在更广范围(或更高层)的上下文序列中重复的能力。比如,在一个应用程序中,一个验证密码的序列很可能出现在多个上下文环境中。如果不能对这些重复的序列进行打包形成单独的单元,你就得对它们进行多次的定义。这不仅需要在系统操作上增加,还使模型的维护更为复杂了。(例如,当序列需要修改时)
  2. 对不同的复杂控制流充分建模的能力,在表现复杂系统的交互性方面很普遍。这包括序列的重复,执行路径的选择,并发和顺序——独立执行等。

幸运的是,关于复杂的交互性问题在电信领域得到了广泛地研究。在多年定义通信协议的时间过程中,形成了一个标准。这个标准被作为主要依据用在UML2.0的交互性描述上。

关键的创新就是把交互性作为单独命名的建模单元进行引入。这样的交互性表现在内部对象间任意复杂的通信。它甚至可以被参数化以用来描述上下文独立的交互模式。

你也可以从更高层递归地调用这些打包了的交互活动,类似于宏调用(图4)。就像你所希望的那样,你可以在任意程度上去进行嵌套。此外,交互活动还能在诸如循环和选择这样的复杂控制的构造中提供操作数(例如,某个给定的交互活动可能需要重复某个具体的次数)。UML 2.0 定义了大量的这种类型的建模构造体,给你在分解后的任何层面上进行复杂的端对端建模,提供了非常大的便利。


图4. 一个复杂交互模型的例子
一个复杂交互模型的例子

图4举例说明了一个扩展的交互模型。在这个例子中,交互活动ATM的访问首先调用另一个低层的叫CheckPIN的处理过程(整个交互活动的内容没有在图中显示),注意到后一个交互活动有一个参数(这个例子中也就是,在处理被取消前,一个无效PIN能被输入的次数)。之后,客户端发送一个同步的消息说明需要哪种交互活动,基于这个具体的值——DispenseCash活动或PayBill活动,将被执行。

在UML 2.0中,交互性不仅由序列图来呈现(如同上面的例子所展示),也通过其他类型的图(包括在UML1中定义的基于协作的形式)来表现。甚至还有通过非图形化的表格来体现。

状态机

添加到UML2.0中的主要新特性与之前的案例非常相似。基本思想是它可以创建一个复合状态的完整模块,它具有清晰的转换入口点和出口点。反过来,你也可以通过一个离散的和可重复使用的状态机的规范来分别地定义上述的复合状态的内在分解。也就是说,在这个状态机或某些其它的状态机中,相同的规范可以在多处重复使用。这样使得在不同上下文中的共享行为模式的规范更加简单化。

在UML2.0中另一个著名的状态机创新是在一个类与它的子类中继承的状态机的分类。

语言特殊化能力

UML1的实践表明了应用UML的一个相当通用的方式是,首先为一个特定的问题或领域定义一个UML Profile ,然后用这个 Profile 代替普通的UML。实质上,这些 Profile 就是一种生成像特定领域语言 (DSL)的方法。

使用UML Profile 的一种可选择的办法是使用MOF标准和工具定义一种新的自定义模型化语言。后者的方法显然有很大的优势,它具有语言定义功能,这种定义可以以最佳的方式解决手头的问题。乍看上去,这种方式似乎是DSL定义的首选方式,但是进一步的观察会发现这种方法存在严重的缺陷。

如简介中所提到的,过多的差异会导致分裂性问题的出现,而设计UML的目的正是要消除这类问题的。

幸运的是,Profile 机制在这里为许多实际的案例提供了一个便利的解决方案。这是因为在不同的DSL之间存在相当多典型的公共部分。例如,实际上任何一个面向对象的建模语言都需要定义类,属性,关联,相互作用等概念。UML,作为一种多用途的建模语言,正好提供了这个便利和有用概念集合的谨慎定义。对多数可能的DSL来说这正是一个好的起点。

但是在这里不仅仅只是概念上的复用,因为从定义的角度,一个UML Profile 必须与标准UML保持一致。换句话说,一个UML Profile 限定了标准UML的概念。这种限定是通过定义上的限制来限制那些给它们提供一种唯一的特殊领域解释的概念。例如,一个限制可能不允许多重继承,或者是它可能需要一个类必须具备一个特殊的属性类型。这就意味着:

  • 任何一种支持标准UML的工具可以基于上述的 Profile 来操纵模型。
  • 任何有关标准UML的知识和实际经验都可以之间应用。

因此,大多数的防止差异上的分裂问题可以完全地减轻或是甚至避免。这种推理方式带来了国际化标准,是形成SDL【ITU02】的原因――这个SDL广泛地在电信上使用――从而重新将SDL定义为一个UML Profile 。【ITU00】【ITU03】。

这并不是说任何一个DSL都能并需要被实现成一个UML Profile ;确实存在很多的案例说明UML可能缺少必备的可以被转换到相应DSL概念中的基础性概念。尽管如此,UML通用性可能比许多人所想象的更广泛。

基于上述考虑,在UML2.0中的 profiling 机制已经被合理化并且它的性能也已经被扩展了。在原型和UML概念之间的连接已经被扩展了。事实上,一个UML2.0原型被定义成好像它仅仅只是一个现有UML元类的子集,并带有关联的属性(代表加有标签的值的标签),操作和限制。这个机制使用比如像OCL这样的语言描述那些限制,它已经被充分的说明了。

除了限制个别的建模概念以外,一个UML2.0框架同样可以明确的隐藏UML概念,从而使得在一个给定的DSL中没有任何意义和必要

最后,UML2.0 profiling 机制同时也可以用作一种机制,它可以从多种不同的域中观察到一个复杂的UML模型――从特定的角度――通常不一定具有DSL。也就是说,任何一个profile 都可以有选择性的以任何方式被应用或是不被应用,只要不影响基础的UML模型。例如,一个性能工程师可能会选择在模型之上通过将多种与性能相关的方法与模型元素连接,来应用一种性能模型化解释。然后可以通过一个自动化技术性能分析工具来决定。

一般性的合并

这一项特性适用于很多的领域,包括重复概念的消除和多数编辑上的修改(比如:给模糊的描述和标准化的术语以及特殊的格式添加相应的说明)

消除了重复和对缺乏定义的概念的说明也是UML2.0另一个重要的需求。受这个需求影响的主要有下面三个主要领域:

  • 动作和活动
  • 模板
  • 基于组件的设计概念

在UML1.5中介绍过动作。动作的概念上的模型被特意的普通化,从而提供数据流和控制流计算模型。这就导致了与活动模型在概念上非常相似。UML2.0利用了这种相似,它为动作和活动提供了一个通用的在语法上和语义上的基础。从你的角度来看,在不同层次上的抽象显得有些过于的形式主义,因为它很典型模拟了不同层次的之间存在的现象。尽管如此,共享概念上的基础使得它完全的简单化和更加的清晰。

在UML 1中,定义模板是非常普遍的:任何的UML 概念都可能产生一个模板。不幸的是,这种普遍性是它应用上的一种阻碍,因为允许存在潜在的无意义的模板类型和可代替的模板。在UML 2.0中的模板机制受到一些容易理解的案例的限制如:分类器,操作,和包。前两种是在流行的程序语言创建模板机制后被模式化的。

基于组件的设计领域中,UML1 有很多使人混淆的概念。你可以使用类,组件,或子系统。这些概念除了在不同方法中有一些微妙的差别外,它们都有着许多共同点。关于在任意的特定情况下来使用,这里没有清楚的描绘。一个子系统仅仅是一个“较大的”组件吗?假如这样的话,毫无疑问的是它在成为一个子系统以前,一个组件要多大呢?类提供了封装和接口的实现,组件和子系统也可以做到。

在UML 2.0中,所有的这些概念都被结合在一起,所以组件被作为一个特殊的例子简单地定义了,即一个结构化类的更加全面的概念;同样地,子系统也仅仅是组件概念的一个特殊例子。两者之间从性质上的不同有清楚的定义,因此,当你使用基于客观的标准概念时,你便能果断地作出决定。

在编辑方面,规范的格式已经统一化了,使用了语法与简单参考资料相结合的模型概念符号规范。每一个元类规范被扩大化了,它明确地确定了语义的不同点,符号的选择权,以及它与UML 1规范的关系。最终,专业术语得到了统一,因此一个特定的条件(例如,类型,实例,规范,或事件)在它出现过的所有上下文中有着相同的全面含义。





回页首


总结

UML 2.0对驱动模型方法做了初步的介绍。那些喜欢将它作为一种绘图工具(正如在文章中先前所描述的那样)的使用者同样也可以像UML 1一样以非正式的方式使用它。此外,尽管新的建模性能是非插入式的,但是在大多数的案例中,这样的使用者将在语言的视觉和感觉上看不到任何变化。

然而,现在MDD阶段性进步的机会在标准化的方法中是可得到的。UML 2.0包含必要的精确性的增强,并且如果你希望你可以使用它的新特性的话――所有的方法都可以完全自动的产生代码。

语言的结构被谨慎的重新编制后允许采用一个模型和渐变的方法:你仅仅需要学习你所感兴趣的那部分语言,其余的你可以完全的忽视。随着经验和知识的增加,你能选择性地添加新的能力。随着重组带来了规范标准定义的极大的简化,它将促进了工具之间的互用性,同时也将促进来自不同商家之间的工具的互用性。

仅有少量新特性被添加里面(用来避免语言冗余),实际上所有这些都遵循相同的递规法则设计原则,从而使你能够模型化既大又复杂的系统。尤其是,这些扩展被添加以促进更加直接地建模软件体系结构,复杂的系统交互,和基于流程的建模,使它在例如商业过程建模和系统工程中被理想化的应用,。

语言扩展机制将被结构重组和简化,以给你提供一种更加直接地方法基于UML来定义特定领域语言(DSL),这些语言在直接的利用丰富的UML工具和专门技术上有着独特的优势。

所有这些导致了第二代建模语言的产生,这种语言将使你更快更可靠的开发成熟的软件系统――同时允许你继续使用相同类型的经验和知识,也是每个软件开发人员所要掌握的技能。




UML 基础: 类图

关于UML 2中结构图的介绍

developerWorks
文档选项
<script language="JavaScript" type="text/javascript"> </script>
将打印机的版面设置成横向打印模式

打印本页

<script language="JavaScript" type="text/javascript"> </script>
将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 高级

Donald Bell, IBM 全球服务, IBM 

2005 年 2 月 15 日

来自Rational Edge:在 UML 2 中,作为新结构图类型的最重要实例,类图可以在整个软件开发生命周期中,被分析师,业务建模人员,开发者和测试者使用的。本文提供了全面的介绍。

Illustration 这是关于统一建模语言、即UML 里采用的基本图的一系列文章的一部分。在我 先前关于序列图的文章 里,我把重点从 UML 1.4 版,转移到 OMG的采用UML 2.0版草案规范(又称为UML 2)。在这篇文章中,我将会讨论结构图,这是已经在 UML 2 中提出的一种新图种类。由于本系列文章的目的是使人们了解记号元素及它们的含意,该文主要关注类图。你很快就会知道这样做的理由。随后的文章将会覆盖结构范畴中包含的其它图。

我也想提醒读者,这一系列文章是关于 UML 记号元素的,所以这些文章并不意味着为建模的最好方式提供指导方针,或是该如何决定哪些内容应该首先被建模。相反的,该文及本系列文章的目的主要是帮助大家对于记号元素 -- 语法和含义有一个基本的理解。借由这些知识,你应该可以阅读图,并使用正确的记号元素创建你自己的图。

这篇文章假定你对面向对象的设计已经有了基本的理解。你们当中如果有人需要一些面向对象概念的帮助,那么可以访问 http://java.sun.com/docs/books/tutorial/java/concepts/,来获得Sun公司关于面向对象编程的简短指导。阅读 “什么是类?” 什么是继承?” 章节,将提供给你足够的理解,并对该文的阅读会有所帮助。另外,David Taylor的书《 Object-Oriented Technologies: A Manager's Guide》提供了面向对象设计的优秀,高水平的说明,而无需对计算机编程有高深的理解。

UML 2 中的阴和阳

参考 UML 基础系列的其他文章和教程

RSS 订阅 UML 相关文章和教程的 RSS 提要

在 UML 2 中有二种基本的图范畴:结构图和行为图。每个 UML 图都属于这二个图范畴。结构图的目的是显示建模系统的静态结构。它们包括类,组件和(或)对象图。另一方面,行为图显示系统中的对象的动态行为,包括如对象的方法,协作和活动之类的内容。行为图的实例是活动图,用例图和序列图。





回页首


大体上的结构图

如同我所说的,结构图显示建模系统的静态结构。关注系统的元件,无需考虑时间。在系统内,静态结构通过显示类型和它们的实例进行传播。除了显示系统类型和它们的实例,结构图至少也显示了这些元素间的一些关系,可能的话,甚至也显示它们的内部结构。

贯穿整个软件生命周期,结构图对于各种团队成员都是有用的。一般而言,这些图支持设计验证,和个体与团队间的设计交流。举例来说,业务分析师可以使用类或对象图,来为当前的资产和资源建模,例如分类账,产品或地理层次。架构师可以使用组件和部署图,来测试/确认他们的设计是否充分。开发者可以使用类图,来设计并为系统的代码(或即将成为代码的)类写文档。

特殊的类图

UML 2 把结构图看成一个分类;这里并不存在称为“结构图”的图。然而,类图提供结构图类型的一个主要实例,并为我们提供一组记号元素的初始集,供所有其它结构图使用。由于类图是如此基本,本文的剩余部分将会把重点集中在类图记号集。在本文的结尾,你将对于如何画UML 2类图有所了解,而且对于理解在后面文章中将涉及的其他结构图有一个稳固的基础。





回页首


基础

如先前所提到的,类图的目的是显示建模系统的类型。在大多数的 UML 模型中这些类型包括:



  • 接口

  • 数据类型

  • 组件

UML 为这些类型起了一个特别的名字:“分类器”。通常地,你可以把分类器当做类,但在技术上,分类器是更为普遍的术语,它还是引用上面的其它三种类型为好。

类名

类的 UML 表示是一个长方形,垂直地分为三个区,如图 1 所示。顶部区域显示类的名字。中间的区域列出类的属性。底部的区域列出类的操作。当在一个类图上画一个类元素时,你必须要有顶端的区域,下面的二个区域是可选择的(当图描述仅仅用于显示分类器间关系的高层细节时,下面的两个区域是不必要的)。图 1 显示一个航线班机如何作为 UML 类建模。正如我们所能见到的,名字是 Flight,我们可以在中间区域看到Flight类的3个属性:flightNumber,departureTime 和 flightDuration。在底部区域中我们可以看到Flight类有两个操作:delayFlight 和 getArrivalTime。

图 1: Flight类的类图

图 1: Flight类的类图

类属性列表

类的属性节(中部区域)在分隔线上列出每一个类的属性。属性节是可选择的,要是一用它,就包含类的列表显示的每个属性。该线用如下格式:

name : attribute type

flightNumber : Integer

继续我们的Flight类的例子,我们可以使用属性类型信息来描述类的属性,如表 1 所示。

表 1:具有关联类型的Flight类的属性名字

属性名称属性类型
flightNumberInteger
departureTimeDate
flightDurationMinutes

在业务类图中,属性类型通常与单位相符,这对于图的可能读者是有意义的(例如,分钟,美元,等等)。然而,用于生成代码的类图,要求类的属性类型必须限制在由程序语言提供的类型之中,或包含于在系统中实现的、模型的类型之中。

在类图上显示具有默认值的特定属性,有时是有用的(例如,在银行账户应用程序中,一个新的银行账户会以零为初始值)。UML 规范允许在属性列表节中,通过使用如下的记号作为默认值的标识:

name : attribute type = default value

举例来说:

balance : Dollars = 0

显示属性默认值是可选择的;图 2 显示一个银行账户类具有一个名为 balance的类型,它的默认值为0。

图 2:显示默认为0美元的balance属性值的银行账户类图。

图 2:显示默认为0美元的balance属性值的银行账户类图。

类操作列表

类操作记录在类图长方形的第三个(最低的)区域中,它也是可选择的。和属性一样,类的操作以列表格式显示,每个操作在它自己线上。操作使用下列记号表现:

	name(parameter list) : type of value returned

下面的表 2 中Flight类操作的映射。

表 2:从图 2 映射的Flight类的操作

操作名称 返回参数 值类型
delayFlight
Name Type
numberOfMinutes Minutes
N/A
getArrivalTime N/A Date

图3显示,delayFlight 操作有一个Minutes类型的输入参数 -- numberOfMinutes。然而,delayFlight 操作没有返回值。 1 当一个操作有参数时,参数被放在操作的括号内;每个参数都使用这样的格式:“参数名:参数类型”。

图 3:Flight类操作参数,包括可选择的“in”标识。

图 3:Flight类操作参数,包括可选择的“in”标识。

当文档化操作参数时,你可能使用一个可选择的指示器,以显示参数到操作的输入参数、或输出参数。这个可选择的指示器以“in”或“out”出现,如图3中的操作区域所示。一般来说,除非将使用一种早期的程序编程语言,如Fortran ,这些指示器可能会有所帮助,否则它们是不必要的。然而,在 C++和Java中,所有的参数是“in”参数,而且按照UML规范,既然“in”是参数的默认类型,大多数人将会遗漏输入/输出指示器。

继承

在面向对象的设计中一个非常重要的概念,继承,指的是一个类(子类)继承另外的一个类(超类)的同一功能,并增加它自己的新功能(一个非技术性的比喻,想象我继承了我母亲的一般的音乐能力,但是在我的家里,我是唯一一个玩电吉他的人)的能力。为了在一个类图上建模继承,从子类(要继承行为的类)拉出一条闭合的,单键头(或三角形)的实线指向超类。考虑银行账户的类型:图 4 显示 CheckingAccount 和 SavingsAccount 类如何从 BankAccount 类继承而来。

图 4: 继承通过指向超类的一条闭合的,单箭头的实线表示。

图 4: 继承通过指向超类的一条闭合的,单箭头的实线表示。

在图 4 中,继承关系由每个超类的单独的线画出,这是在IBM Rational Rose和IBM Rational XDE中使用的方法。然而,有一种称为 树标记的备选方法可以画出继承关系。当存在两个或更多子类时,如图 4 中所示,除了继承线象树枝一样混在一起外,你可以使用树形记号。图 5 是重绘的与图 4 一样的继承,但是这次使用了树形记号。

图 5: 一个使用树形记号的继承实例

图 5: 一个使用树形记号的继承实例

抽象类及操作
细心的读者会注意到,在图 4 和 图5 中的图中,类名BankAccount和withdrawal操作使用斜体。这表示,BankAccount 类是一个抽象类,而withdrawal方法是抽象的操作。换句话说,BankAccount 类使用withdrawal规定抽象操作,并且CheckingAccount 和 SavingsAccount 两个子类都分别地执行它们各自版本的操作。

然而,超类(父类)不一定要是抽象类。标准类作为超类是正常的。

关联
当你系统建模时,特定的对象间将会彼此关联,而且这些关联本身需要被清晰地建模。有五种关联。在这一部分中,我将会讨论它们中的两个 -- 双向的关联和单向的关联,而且我将会在Beyond the basics部分讨论剩下的三种关联类型。请注意,关于何时该使用每种类型关联的详细讨论,不属于本文的范围。相反的,我将会把重点集中在每种关联的用途,并说明如何在类图上画出关联。

双向(标准)的关联
关联是两个类间的联接。关联总是被假定是双向的;这意味着,两个类彼此知道它们间的联系,除非你限定一些其它类型的关联。回顾一下Flight 的例子,图 6 显示了在Flight类和Plane类之间的一个标准类型的关联。

图 6:在一个Flight类和Plane类之间的双向关联的实例

图 6:在一个Flight类和Plane类之间的双向关联的实例

一个双向关联用两个类间的实线表示。在线的任一端,你放置一个角色名和多重值。图 6 显示Flight与一个特定的Plane相关联,而且Flight类知道这个关联。因为角色名以Plane类表示,所以Plane承担关联中的“assignedPlane”角色。紧接于Plane类后面的多重值描述0...1表示,当一个Flight实体存在时,可以有一个或没有Plane与之关联(也就是,Plane可能还没有被分配)。图 6 也显示Plane知道它与Flight类的关联。在这个关联中,Flight承担“assignedFlights”角色;图 6 的图告诉我们,Plane实体可以不与flight关联(例如,它是一架全新的飞机)或与没有上限的flight(例如,一架已经服役5年的飞机)关联。

由于对那些在关联尾部可能出现的多重值描述感到疑惑,下面的表3列出了一些多重值及它们含义的例子。

表 3: 多重值和它们的表示

可能的多重值描述
表示含义
0..1 0个或1个
1只能1个
0..*0个或多个
* 0个或多个
1..*1个或我个
3只能3个
0..50到5个
5..15 5到15个

单向关联
在一个单向关联中,两个类是相关的,但是只有一个类知道这种联系的存在。图 7 显示单向关联的透支财务报告的一个实例。

图 7: 单向关联一个实例:OverdrawnAccountsReport 类 BankAccount 类,而 BankAccount 类则对关联一无所知。

图 7: 单向关联一个实例:OverdrawnAccountsReport 类 BankAccount 类,而 BankAccount 类则对关联一无所知。

一个单向的关联,表示为一条带有指向已知类的开放箭头(不关闭的箭头或三角形,用于标志继承)的实线。如同标准关联,单向关联包括一个角色名和一个多重值描述,但是与标准的双向关联不同的时,单向关联只包含已知类的角色名和多重值描述。在图 7 中的例子中,OverdrawnAccountsReport 知道 BankAccount 类,而且知道 BankAccount 类扮演“overdrawnAccounts”的角色。然而,和标准关联不同,BankAccount 类并不知道它与 OverdrawnAccountsReport 相关联。 2

软件包
不可避免,如果你正在为一个大的系统或大的业务领域建模,在你的模型中将会有许多不同的分类器。管理所有的类将是一件令人生畏的任务;所以,UML 提供一个称为 软件包的组织元素。软件包使建模者能够组织模型分类器到名字空间中,这有些象文件系统中的文件夹。把一个系统分为多个软件包使系统变成容易理解,尤其是在每个软件包都表现系统的一个特定部分时。 3

在图中存在两种方法表示软件包。并没有规则要求使用哪种标记,除了用你个人的判断:哪种更便于阅读你画的类图。两种方法都是由一个较小的长方形(用于定位)嵌套在一个大的长方形中开始的,如图 8 所示。但是建模者必须决定包的成员如何表示,如下:

  • 如果建模者决定在大长方形中显示软件包的成员,则所有的那些成员 4 需要被放置在长方形里面。另外,所有软件包的名字需要放在软件包的较小长方形之内(如图 8 的显示)。

  • 如果建模者决定在大的长方形之外显示软件包成员,则所有将会在图上显示的成员都需要被置于长方形之外。为了显示属于软件包的分类器属于,从每个分类器画一条线到里面有加号的圆周,这些圆周粘附在软件包之上(图9)。
图 8:在软件包的长方形内显示软件包成员的软件包元素例子

图 8:在软件包的长方形内显示软件包成员的软件包元素例子

图 9:一个通过连接线表现软件包成员的软件包例子

图 9:一个通过连接线表现软件包成员的软件包例子

了解基础重要性

在 UML 2 中,了解类图的基础更为重要。这是因为类图为所有的其他结构图提供基本的构建块。如组件或对象图(仅仅是举了些例子)。





回页首


超过基础

到此为止,我已经介绍了类图的基础,但是请继续往下读!在下面的部分中,我将会引导你到你会使用的类图的更重要的方面。这些包括UML 2 规范中的接口,其它的三种关联类型,可见性和其他补充。

接口
在本文的前面,我建议你以类来考虑分类器。事实上,分类器是一个更为一般的概念,它包括数据类型和接口。

关于何时、以及如何高效地在系统结构图中使用数据类型和接口的完整讨论,不在本文的讨论范围之内。既然这样,我为什么要在这里提及数据类型和接口呢?你可能想在结构图上模仿这些分类器类型,在这个时候,使用正确的记号来表示,或者至少知道这些分类器类型是重要的。不正确地绘制这些分类器,很有可能将使你的结构图读者感到混乱,以后的系统将不能适应需求。

一个类和一个接口不同:一个类可以有它形态的真实实例,然而一个接口必须至少有一个类来实现它。在 UML 2 中,一个接口被认为是类建模元素的特殊化。因此,接口就象类那样绘制,但是长方形的顶部区域也有文本“interface”,如图 10 所示。 5

图 10:Professor类和Student类实现Person接口的类图实例

图 10:Professor类和Student类实现Person接口的类图实例

在图 10 中显示的图中,Professor和Student类都实现了Person的接口,但并不从它继承。我们知道这一点是由于下面两个原因:1) Person对象作为接口被定义 -- 它在对象的名字区域中有“interface”文本,而且我们看到由于Professor和Student对象根据画类对象的规则(在它们的名字区域中没有额外的分类器文本)标示,所以它们是 对象。 2) 我们知道继承在这里没有被显示,因为与带箭头的线是点线而不是实线。如图 10 所示,一条带有闭合的单向箭头的 线意味着实现(或实施);正如我们在图 4 中所见到的,一条带有闭合单向箭头的线表示继承。

更多的关联
在上面,我讨论了双向关联和单向关联。现在,我将会介绍剩下的三种类型的关联。

关联类
在关联建模中,存在一些情况下,你需要包括其它类,因为它包含了关于关联的有价值的信息。对于这种情况,你会使用 关联类 来绑定你的基本关联。关联类和一般类一样表示。不同的是,主类和关联类之间用一条相交的点线连接。图 11 显示一个航空工业实例的关联类。

图 11:增加关联类 MileageCredit

图 11:增加关联类 MileageCredit

在图 11 中显示的类图中,在Flight类和 FrequentFlyer 类之间的关联,产生了称为 MileageCredit的关联类。这意味当Flight类的一个实例关联到 FrequentFlyer 类的一个实例时,将会产生 MileageCredit 类的一个实例。

聚合
聚合是一种特别类型的关联,用于描述“总体到局部”的关系。在基本的聚合关系中, 部分类 的生命周期独立于 整体类 的生命周期。

举例来说,我们可以想象, 是一个整体实体,而 车轮 轮胎是整辆车的一部分。轮胎可以在安置到车时的前几个星期被制造,并放置于仓库中。在这个实例中,Wheel类实例清楚地独立地Car类实例而存在。然而,有些情况下, 部分 类的生命周期并 独立于 整体 类的生命周期 -- 这称为合成聚合。举例来说,考虑公司与部门的关系。 公司和部门 都建模成类,在公司存在之前,部门不能存在。这里Department类的实例依赖于Company类的实例而存在。

让我们更进一步探讨基本聚合和组合聚合。

基本聚合
有聚合关系的关联指出,某个类是另外某个类的一部分。在一个聚合关系中,子类实例可以比父类存在更长的时间。为了表现一个聚合关系,你画一条从父类到部分类的实线,并在父类的关联末端画一个未填充棱形。图 12 显示车和轮胎间的聚合关系的例子。

图 12: 一个聚合关联的例子

图 12: 一个聚合关联的例子

组合聚合
组合聚合关系是聚合关系的另一种形式,但是子类实例的生命周期依赖于父类实例的生命周期。在图13中,显示了Company类和Department类之间的组合关系,注意组合关系如聚合关系一样绘制,不过这次菱形是被填充的。

图 13: 一个组合关系的例子

图 13: 一个组合关系的例子

在图 13 中的关系建模中,一个Company类实例至少总有一个Department类实例。因为关系是组合关系,当Company实例被移除/销毁时,Department实例也将自动地被移除/销毁。组合聚合的另一个重要功能是部分类只能与父类的实例相关(举例来说,我们例子中的Company类)。

反射关联
现在我们已经讨论了所有的关联类型。就如你可能注意到的,我们的所有例子已经显示了两个不同类之间的关系。然而,类也可以使用反射关联与它本身相关联。起先,这可能没有意义,但是记住,类是抽象的。图 14 显示一个Employee类如何通过manager / manages角色与它本身相关。当一个类关联到它本身时,这并不意味着类的实例与它本身相关,而是类的一个实例与类的另一个实例相关。

图 14:一个反射关联关系的实例

图 14:一个反射关联关系的实例

图 14 描绘的关系说明一个Employee实例可能是另外一个Employee实例的经理。然而,因为“manages”的关系角色有 0..*的多重性描述;一个雇员可能不受任何其他雇员管理。

可见性
在面向对象的设计中,存在属性及操作可见性的记号。UML 识别四种类型的可见性:public,protected,private及package。

UML 规范并不要求属性及操作可见性必须显示在类图上,但是它要求为每个属性及操作定义可见性。为了在类图上的显示可见性,放置可见性标志于属性或操作的名字之前。虽然 UML 指定四种可见性类型,但是实际的编程语言可能增加额外的可见性,或不支持 UML 定义的可见性。表4显示了 UML 支持的可见性类型的不同标志。

表 4:UML 支持的可见性类型的标志

标志可见性类型
+Public
# Protected
- Private
~Package

现在,让我们看一个类,以说明属性及操作的可见性类型。在图 15 中,所有的属性及操作都是public,除了 updateBalance 操作。updateBalance 操作是protected。

图 15:一个 BankAccount 类说明它的属性及操作的可见性

图 15:一个 BankAccount 类说明它的属性及操作的可见性





回页首


UML 2 补充

既然我们已经覆盖了基础和高级主题,我们将覆盖一些由UML 1. x增加的类图的新记号。

实例
当一个系统结构建模时,显示例子类实例有时候是有用的。为了这种结构建模,UML 2 提供 实例规范 元素,它显示在系统中使用例子(或现实)实例的值得注意的信息。

实例的记号和类一样,但是取代顶端区域中仅有的类名,它的名字是经过拼接的:

Instance Name : Class Name

举例来说:

Donald : Person

因为显示实例的目的是显示值得注意的或相关的信息,没必要在你的模型中包含整个实体属性及操作。相反地,仅仅显示感兴趣的属性及其值是完全恰当的。如图16所描述。

图 16:Plane类的一个实例例子(只显示感兴趣的属性值)

图 16:Plane类的一个实例例子(只显示感兴趣的属性值)

然而,仅仅表现一些实例而没有它们的关系不太实用;因此,UML 2 也允许在实体层的关系/关联建模。绘制关联与一般的类关系的规则一样,除了在建模关联时有一个附加的要求。附加的限制是,关联关系必须与类图的关系相一致,而且关联的角色名字也必须与类图相一致。它的一个例子显示于图 17 中。在这个例子中,实例是图 6 中类图的例子实例。

图 17:图 6 中用实例代替类的例子

图 17:图 6 中用实例代替类的例子

图 17 有Flight类的二个实例,因为类图指出了在Plane类和Flight类之间的关系是 0或多。因此,我们的例子给出了两个与NX0337 Plane实例相关的Flight实例。

角色
建模类的实例有时比期望的更为详细。有时,你可能仅仅想要在一个较多的一般层次做类关系的模型。在这种情况下,你应该使用 角色 记号。角色记号类似于实例记号。为了建立类的角色模型,你画一个方格,并在内部放置类的角色名及类名,作为实体记号,但是在这情况你不能加下划线。图 18 显示一个由图 14 中图描述的雇员类扮演的角色实例。在图 18 中,我们可以认为,即使雇员类与它本身相关,关系确实是关于雇员之间扮演经理及团队成员的角色。

图 18:一个类图显示图14中扮演不同角色的类

图 18:一个类图显示图14中扮演不同角色的类

注意,你不能在纯粹类图中做类角色的建模,即使图 18显示你可以这么做。为了使用角色记号,你将会需要使用下面讨论的内部结构记号。

内部的结构
UML 2 结构图的更有用的功能之一是新的内部结构记号。它允许你显示一个类或另外的一个分类器如何在内部构成。这在 UML 1. x 中是不可能的,因为记号限制你只能显示一个类所拥有的聚合关系。现在,在 UML 2 中,内部的结构记号让你更清楚地显示类的各个部分如何保持关系。

让我们看一个实例。在图 18 中我们有一个类图以表现一个Plane类如何由四个引擎和两个控制软件对象组成。从这个图中省略的东西是显示关于飞机部件如何被装配的一些信息。从图 18 的图,你无法说明,是每个控制软件对象控制两个引擎,还是一个控制软件对象控制三个引擎,而另一个控制一个引擎。

图 19: 只显示对象之间关系的类图

图 19: 只显示对象之间关系的类图

绘制类的内在结构将会改善这种状态。开始时,你通过用二个区域画一个方格。最顶端的区域包含类名字,而较低的区域包含类的内部结构,显示在它们父类中承担不同角色的部分类,角色中的每个部分类也关系到其它类。图 19 显示了Plane类的内部结构;注意内部结构如何澄清混乱性。

图 20:Plane类的内部结构例子。

图 20:Plane类的内部结构例子。

在图 20 中Plane有两个 ControlSoftware 对象,而且每个控制二个引擎。在图左边上的 ControlSoftware(control1)控制引擎 1 和 2 。在图右边的 ControlSoftware(control2)控制引擎 3 和 4 。





回页首


结论

至少存在两个了解类图的重要理由。第一个是它显示系统分类器的静态结构;第二个理由是图为UML描述的其他结构图提供了基本记号。开发者将会认为类图是为他们特别建立的;但是其他的团队成员将发现它们也是有用的。业务分析师可以用类图,为系统的业务远景建模。正如我们将会在本系列关于 UML 基础的文章中见到的,其他的图 -- 包括活动图,序列图和状态图——参考类图中的类建模和文档化。

关于“UML 基础”的本系列的后面的元件图。





回页首

脚注

1 delayFlight没有返回值,因为我作出了设计决定,不要返回值。有一点可以争论的是,延迟操作应该返回新的到达时间,而且,如果是这种情形,操作属性将显示为 delayFlight(numberOfMinutes : Minutes) : Date。

2可能看起来很奇怪, BankAccount 类不知道 OverdrawnAccountsReport 类。这个建模使报表类可以知道它们报告的业务类,但是业务类不知道它们正在被报告。这解开两个对象的耦合,并因此使系统变得更能适应变化。

3 软件包对于组织你的模型类是庞大的,但是记住重要的一点是,你的类图应该是关于建模系统的容易交流的信息。在你的软件包有许多类的情况下,最好使用多个主题类图,而不是仅仅产生一个大的类图。

4 要理解重要一点,当我说“所有的那些成员”时,我仅仅意味着在当前图中的类将显示出来。显示一个有内容的软件包的图,不需要显示它的所有内容。它可以依照一些准则,显示包含元素的子集,这个准则就是并非所有的软件包分类器都是必需的。

5 当画一个类图时,在 UML 规范中,全部要做的只是把类放入长方形的顶部区域,而你同理处理接口;然而,UML 规范认为,在这个区域放置“class”文本是可选的,如果类没有显示,那么它应该被假设。




UML 基础: 序列图

developerWorks
文档选项
<script language="JavaScript" type="text/javascript"> </script>
将打印机的版面设置成横向打印模式

打印本页

<script language="JavaScript" type="text/javascript"> </script>
将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

Donald Bell, IBM 全球服务, IBM 

2005 年 2 月 15 日

本文来自于 Rational Edge本文作为 UML 基础的、关于统一建模语言的基础图的一系列文章的一部分,提供对序列图(Sequence Diagram,又称为 顺序图)的详细介绍。它也介绍了最近的 UML 2.0 规范的几个新符号元件。

Illustration


现在是二月,而且到如今你或许已经读到、或听到人们谈论UML 2.0 —— 包括若干进步的 UML 的新规范,所做的变化。考虑到新规范的重要性,我们也正在修改这个文章系列的基础,把我们的注意力从 OMG 的 UML 1.4 规范,转移到 OMG 的已采纳 UML 2.0草案规范(又名 UML 2)。我不喜欢在一系列文章的中间,把重点从 1.4 变为 2.0 ,但是 UML 2.0 草案规范是前进的重要一步,我感觉需要扩充文字。

由于一些理由,OMG 改良了 UML 。主要的理由是,他们希望 UML 模型能够表达模型驱动架构(MDA),这意味着 UML 必须支持更多的模型驱动的符号。同时, UML 1.x 符号集合有时难以适用于较大的应用程序。此外,为了要使图变成更容易阅读,需要改良符号元件。(举例来说,UML 1.x 的模型逻辑流程太复杂,有时不可能完成。对UML 2 中的序列图的符号集合的改变,已经在序列化逻辑建模方面取得巨大的进步)。

注意我上面所述的文字:“已采纳UML2.0草案规范。”确实,规范仍然处于草案状态,但是关键是草案规范已经被 OMG 采用,OMG是一个直到新标准相当可靠,才会采用它们的组织。 在 UML 2 完全地被采用之前,规范将会有一些修改,但是这些改变应该是极小的。主要的改变将会是在 UML 的内部 —— 包括通常被实施 UML 工具的软件公司使用的功能。

本文的主要目的是继续把我们的重点放在基础UML图上;这个月,我们进一步了解序列图。再次请注意,下面提供的例子正是以新的 UML 2 规范为基础。

图的目的

参考 UML 基础系列的其他文章和教程

RSS 订阅 UML 相关文章和教程的 RSS 提要

序列图主要用于按照交互发生的一系列顺序,显示对象之间的这些交互。很象类图,开发者一般认为序列图只对他们有意义。然而,一个组织的业务人员会发现,序列图显示不同的业务对象如何交互,对于交流当前业务如何进行很有用。除记录组织的当前事件外,一个业务级的序列图能被当作一个需求文件使用,为实现一个未来系统传递需求。在项目的需求阶段,分析师能通过提供一个更加正式层次的表达,把用例带入下一层次。那种情况下,用例常常被细化为一个或者更多的序列图。

组织的技术人员能发现,序列图在记录一个未来系统的行为应该如何表现中,非常有用。在设计阶段,架构师和开发者能使用图,挖掘出系统对象间的交互,这样充实整个系统设计。

序列图的主要用途之一,是把用例表达的需求,转化为进一步、更加正式层次的精细表达。用例常常被细化为一个或者更多的序列图。序列图除了在设计新系统方面的用途外,它们还能用来记录一个存在系统(称它为“遗产”)的对象现在如何交互。当把这个系统移交给另一个人或组织时,这个文档很有用。





回页首


符号

既然这是我基于 UML 2的 UML 图系列文章的第一篇,我们需要首先讨论对 UML 2 图符号的一个补充,即一个叫做框架的符号元件。在 UML 2中,框架元件用于作为许多其他的图元件的一个基础,但是大多数人第一次接触框架元件的情况,是作为图的图形化边界。当为图提供图形化边界时,一个框架元件为图的标签提供一致的位置。在 UML 图中框架元件是可选择的;就如你能在图 1 和 2 中见到的,图的标签被放在左上角,在我将调用框架的“namebox”中,一种卷角长方形,而且实际的 UML 图在较大的封闭长方形内部定义。

图 1: 空的 UML 2 框架元件

图 1: 空的 UML 2 框架元件

除了提供一个图形化边框之外,用于图中的框架元件也有描述交互的重要的功能, 例如序列图。在序列图上一个序列接收和发送消息(又称交互),能通过连接消息和框架元件边界,建立模型(如图 2 所见到)。这将会在后面“超越基础”的段落中被更详细地介绍。

图 2: 一个接收和发送消息的序列图

图 2: 一个接收和发送消息的序列图

注意在图 2 中,对于序列图,图的标签由文字“sd”开始。当使用一个框架元件封闭一个图时,图的标签需要按照以下的格式:

图类型 图名称

UML 规范给图类型提供特定的文本值。(举例来说,sd代表序列图,activity代表活动图,use case代表用例图)。





回页首


基础

序列图的主要目的是定义事件序列,产生一些希望的输出。重点不是消息本身,而是消息产生的顺序;不过,大多数序列图会表示一个系统的对象之间传递的什么消息,以及它们发生的顺序。图按照水平和垂直的维度传递信息:垂直维度从上而下表示消息/调用发生的时间序列,而且水平维度从左到右表示消息发送到的对象实例。

生命线

当画一个序列图的时候,放置生命线符号元件,横跨图的顶部。生命线表示序列中,建模的角色或对象实例。 1 生命线画作一个方格,一条虚线从上而下,通过底部边界的中心(图 3)。生命线名字放置在方格里。

图 3: 用于一个实体名为freshman的生命线的Student类的一个例子

图 3: 用于一个实体名为freshman的生命线的Student类的一个例子

UML 的生命线命名标准按照如下格式:

实体名 : 类名

在如图3所示的例子中,生命线表示类Student的实体,它的实体名称是freshman。这里注意一点,生命线名称带下划线。当使用下划线时,意味着序列图中的生命线代表一个类的特定实体,不是特定种类的实体(例如,角色)。在将来的一篇文章中,我们将会了解结构化建模。现在,仅仅评述序列图,可能包含角色(例如买方卖方),而不需要叙述谁扮演那些角色(例如BillFred)。这准许不同语境的图重复使用。简单拖放,序列图的实例名称有下划线,而角色名称没有。

图 3 中我们生命线例子是一个命名的对象,但是不是所有的生命线都代表命名的对象。相反的,一个生命线能用来表现一个匿名的或未命名的实体。当在一个序列图上,为一个未命名的实例建模时,生命线的名字采用和一个命名实例相同的模式;但是生命线名字的位置留下空白,而不是提供一个例图名字。再次参考图 3,如果生命线正在表现Student类的一个匿名例图,生命线会是: “Student”。同时, 因为序列图在项目设计阶段中使用,有一个未指定的对象是完全合法: 举例来说,“freshman”。

消息

为了可读性,序列图的第一个消息总是从顶端开始,并且一般位于图的左边。然后继发的消息加入图中,稍微比前面的消息低些。

为了显示一个对象(例如,生命线)传递一个消息给另外一个对象,你画一条线指向接收对象,包括一个实心箭头(如果是一个同步调用操作)或一个棍形箭头(如果是一个异步讯号)。消息/方法名字放置在带箭头的线上面。正在被传递给接收对象的消息,表示接收对象的类实现的一个操作/方法。在图 4 的例子中,analyst对象调用ReportingSystem 类的一个实例的系统对象。analyst对象在调用系统对象的 getAvailableReports 方法。系统对象然后调用secSystem 对象上的、包括参数userId的getSecurityClearance 方法,secSystem的类的类型是 SecuritySystem。 2

图 4: 一个在对象之间传递消息的实例

图 4: 一个在对象之间传递消息的实例

除了仅仅显示序列图上的消息调用外,图 4 中的图还包括返回消息。这些返回消息是可选择的;一个返回消息画作一个带开放箭头的虚线,向后指向来源的生命线,在这条虚线上面,你放置操作的返回值。在图 4 中,当 getSecurityClearance 方法被调用时,secSystem 对象返回 userClearance 给系统对象。当 getAvailableReports 方法被调用时,系统对象返回 availableReports。

此外,返回消息是序列图的一个可选择部分。返回消息的使用依赖建模的具体/抽象程度。如果需要较好的具体化,返回消息是有用的;否则,主动消息就足够了。我个人喜欢,无论什么时候返回一个值,都包括一个返回消息,因为我发现额外的细节使一个序列图变得更容易阅读。

当序列图建模时,有时候,一个对象将会需要传递一个消息给它本身。一个对象何时称它本身?一个纯化论者会争辩一个对象应该永不传递一个消息给它本身。然而,为传递一个消息给它本身的对象建模,在一些情境中可能是有用的。举例来说,图 5 是图 4 的一个改良版本。 图 5 版本显示调用它的 determineAvailableReports 方法的系统对象。通过表示系统传递消息“determineAvailableReports”给它本身,模型把注意力集中到过程的事实上,而不是系统对象。

为了要画一个调用本身的对象,如你平时所作的,画一条消息,但是不是连接它到另外的一个对象,而是你把消息连接回对象本身。

图 5: 系统对象调用它的 determineAvailableReports 方法

图 5: 系统对象调用它的 determineAvailableReports 方法

图 5 中的消息实例显示同步消息;然而,在序列图中,你也能为异步消息建模。一个异步消息和一个同步的画法类似,但是消息画的线带一个棍形矛头,如图 6 所示。

图 6: 表示传递到实体2的异步消息的序列图片段

图 6: 表示传递到实体2的异步消息的序列图片段

约束

当为对象的交互建模时,有时候,必须满足一个条件,消息才会传递给对象。约束在 UML 图各处中,用于控制流。在这里,我将会讨论UML 1.x 及UML 2.0两者的约束。在 UML 1.x 中,一个约束只可能被分配到一个单一消息。UML 1.x中,为了在一个序列图上画一个约束,你把约束元件放在约束的消息线上,消息名字之前。图 7 显示序列图的一个片段,消息addStudent 方法上有一个约束。

图 7:UML 1.x 序列图的一个片段,其中addStudent 消息有一个约束

图 7:UML 1.x 序列图的一个片段,其中addStudent 消息有一个约束

在图 7 中,约束是文本“[ pastDueBalance=0]”。通过这个消息上的约束,如果应收帐系统返回一个零点的逾期平衡,addStudent 消息才将会被传递。约束的符号很简单;格式是:

[Boolean Test]

举例来说,

[pastDueBalance = 0]

组合碎片(变体方案,选择项,和循环)

然而,在大多数的序列图中,UML 1.x“in-line”约束不足以处理一个建模序列的必需逻辑。这个功能缺失是 UML 1.x 的一个问题。UML 2 已经通过去掉“in-line”约束,增加一个叫做组合碎片的符号元件,解决了这一个问题。一个组合碎片用来把一套消息组合在一起,在一个序列图中显示条件分支。UML 2 规范指明了组合碎片的 11 种交互类型。十一种中的三种将会在“基础”段落中介绍,另外两种类型将会在“超越基础”中介绍,而那剩余的六种我将会留在另一篇文章中介绍。(嗨,这是一篇文章而不是一本书。我希望你在一天中看完这部分!)

变体

变体用来指明在两个或更多的消息序列之间的、互斥的选择。 3 变体支持经典的“if then else”逻辑的建模(举例来说,如果 我买三个,然后 我得到 我购买的20% 折扣;否则 我得到我购买的 10% 折扣)。

就如你将会在图 8 中注意到的,一个变体的组合碎片元件使用框架来画。单词“alt”放置在框架的namebox里。然后较大的长方形分为 UML 2 所称的操作元。 4 操作元被虚线分开。每个操作元有一个约束进行测试,而这个约束被放置在生命线顶端的操作元的左上部。 5 如果操作元的约束等于“true”,然后那个操作元是要执行的操作元。

图 8:包含变体组合碎片的一个序列图片段

图 8:包含变体组合碎片的一个序列图片段

图 8作为一个变体的组合碎片如何阅读的例子,显示序列从顶部开始,即bank对象获取支票金额和帐户结余。此时,序列图中的变体组合碎片接管。因为约束“[balance >= amount]”,如果余额超过或等于金额,然后顺序进行bank对象传递 addDebitTransaction 和 storePhotoOfCheck 消息给account对象。然而,如果余额不是超过或等于金额,然后顺序的过程就是bank传递addInsuffientFundFee 和 noteReturnedCheck 消息给account对象,returnCheck 消息给它自身。因为“else”约束,当余额不大于或者等于金额时,第二个序列被调用。在变体的组合碎片中,不需要“else”约束;而如果一个操作元,在它上面没有一个明确的约束,那么将假定“else”约束。

变体的组合碎片没被限制在简单的“if then else”验证。可能需要大量的变体路径。 如果需要较多的变体方案,你一定要做的全部工作就是把一个操作元加入有序列约束和消息的长方形中。

选择项

选择项组合碎片用来为序列建模,这些序列给予一个特定条件,将会发生的;或者,序列不发生。一个选择项用来为简单的“if then”表达式建模。(例如,如果架上的圈饼少于五个,那么另外做两打圈饼)。

选择项组合碎片符号与变体组合碎片类似,除了它只有一个操作元并且永不能有“else”约束以外(它就是如此,没有理由)。要画选择项组合,你画一个框架。文字“opt”是被放置在框架的 namebox 里的文本,在框架的内容区,选择项的约束被放置在生命线顶端上的左上角。 然后选择项的消息序列被放在框架的内容区的其余位置内。这些元件如图 9 所示。

图 9:包括选择项组合碎片的一个序列图片段

图 9:包括选择项组合碎片的一个序列图片段

阅读选择项组合碎片很容易。图 9 是图 7 的序列图片段的再加工,但是这次它使用一个选择项组合碎片,因为如果Student的逾期平衡等于0,需要传递更多的消息。按照图 9 的序列图,如果Student的逾期平衡等于零,然后传递addStudent,getCostOfClass和chargeForClass消息。如果Student的逾期平衡不等于零,那么在选择项组合碎片中,序列不传递任何一个消息。

例子图 9的序列图片段包括一个选择项约束;然而,约束不是一个必需的元件。在高层次、抽象的序列图中,你可能不想叙述选择项的条件。你可能只是想要指出片段是可选择的。

循环

有时候你将会需要为一个重复的序列建模。在 UML 2 中,为一个重复的序列建模已经改良,附加了循环组合碎片。

循环组合碎片表面非常类似选择项组合碎片。你画一个框架,在框架的 namebox 中放置文本“loop”。在框架的内容区中,一个生命线的顶部,循环约束 6 被放置在左上角。然后循环的消息序列被放在框架内容区的其余部分中。在一个循环中,除了标准的布尔测试外,一个约束能测试二个特定的条件式。特定的约束条件式是写作“minint = [the number]”(例如,“minint = 1”)的最小循环次数,和写作“maxint = [the number]”(例如,“maxint = 5”)的最大循环次数。通过最小循环检验,循环必须运行至少指定次数,而循环执行次数不能达到约束指定的最大循环次数。

图 10:循环组合碎片的一个序列图例子

图 10:循环组合碎片的一个序列图例子

点击放大

在图 10 中显示的循环运行,直到 reportsEnu 对象的 hasAnotherReport 消息返回false。如果循环序列应该运行,这个序列图的循环使用一个布尔测试确认。为了阅读这个图,你和平常一样,从顶部开始。当你到达循环组合碎片,做一个测试,看看值 hasAnotherReport 是否等于true。如果 hasAnotherReport 值等于true,于是序列进入循环片断。然后你能和正常情况一样,在序列图中跟踪循环的消息。





回页首


超越基础

我已经介绍了序列图的基础,应该使你可以为将会在系统中通常发生的大部份交互建模。下面段落将会介绍用于序列图的比较高阶的符号元件。

引用另外一个序列图

当做序列图的时候,开发者爱在他们的序列图中,重用存在的序列图。 7 在 UML 2 中开始,引进“交互进行”元件。追加交互进行的可以说是 UML 2 交互建模中的最重要的创新。交互进行增加了功能,把原始的序列图组织成为复杂的序列图。由于这些,你能组合(重用)较简单的序列,生成比较复杂的序列。这意味你能把完整的、可能比较复杂的序列,抽象为一个单一的概念单位。

一个交互进行元件使用一个框架绘制。文字“ref”放置在框架的 namebox 中,引用的序列图名字放置在框架的内容区里,连同序列图的任何参数一起。引用序列图的名字符号如下模式:

序列图名[(参数)] [: 返回值]

两个例子:

1. Retrieve Borrower Credit Report(ssn) : borrowerCreditReport

或者

2. Process Credit Card(name, number, expirationDate, amount : 100)

在例子 1 中,语法调用叫做Retrieve Borrower Credit Report的序列图,传递给它参数 ssn。序列Retreive Borrower Credit Report返回变量 borrowerCreditReport 。

在实例 2 中,语法调用叫做Process Credit Card的序列图,传递给它参数name,number,expiration date,和 amount。然而,在例子 2 中,amount参数将会是值100。因为例子2没有返回值标签,序列不返回值(假设,建模的序列不需要返回值)。

图 11: 一个引用两个不同序列图的序列图

图 11: 一个引用两个不同序列图的序列图

图 11 显示一个序列图,它引用了序列图“Balance Lookup”和“Debit Account”。序列从左上角开始,客户传递一个消息给teller对象。teller对象传递一个消息给 theirBank 对象。那时,调用Balance Lookup序列图,而 accountNumber作为一个参数传递。Balance Lookup序列图返回balance变量。然后检验选择项组合碎片的约束条件,确认余额大于金额变量。在余额比金额更大的情况下,调用Debit Account序列图,给它传递参数accountNumber 和amount。在那个序列完成后,withdrawCash 消息为客户返回cash。

重要的是,注意在图 11 中,theirBank 的生命线被交互进行Balance Lookup隐藏了。因为交互进行隐藏生命线,意味着theirBank 生命线在“Balance Lookup”序列图中被引用。除了隐藏交互进行的生命线之外,UML 2 也指明,生命线在它自己的“Balance Lookup”序列中,一定有相同的 theirBank 。

有时候,你为一个序列图建模,其中交互进行会重叠没有 在交互进行中引用的生命线。在那种情况下,生命线和正常的生命线一样显示,不会被重叠的交互进行隐藏。

在图 11 中,序列引用“Balance Lookup”序列图。“Balance Lookup”序列图在图 12 中显示。因为例子序列有参数和一个返回值,它的标签 —— 位于图的 namebox 中 —— 按照一个特定模式:

图类型 图名 [参数类型:参数名]

[: 返回值类型]

两个例子:

1. SD Balance Lookup(Integer : accountNumber) : Real

2. SD Available Reports(Financial Analyst : analyst) : Reports

图 12 举例说明例子 1,在里面,Balance Lookup序列把参数 accountNumber 作为序列中的变量使用,序列图显示返回的Real对象。在类似这种情况下,返回的对象采用序列图实体名。

图 12: 一个使用 accountNumber 参数并返回一个Real对象的序列图

图 12: 一个使用 accountNumber 参数并返回一个Real对象的序列图

图 13 举例说明例子 2,在里面,一个序列图获取一个参数,返回一个对象。然而,在图 13 中参数在序列的交互中使用。

图 13: 一个在它的交互中使用参数、返回一个Reports对象的序列图

图 13: 一个在它的交互中使用参数、返回一个Reports对象的序列图

点击放大

前面的段落展示如何通过参数和返回值传递信息,引用另一个序列图。然而,有另一个方法在序列图之间传递消息。门可能是一个容易的方法,为在序列图和它的上下文之间的传递消息建模。一个门只是一个消息,图形表示为一端连接序列图的框架边缘,另一端连接到生命线。使用门的图 11 和 12 ,在图 14 和 15 中可以被看到重构。图 15 的例图有一个叫做getBalance的入口门,获取参数 accountNumber。因为是箭头的线连接到图的框架,而箭头连接到生命线,所以 getBalance 消息是一个入口门。序列图也有一个出囗门,返回balance变量。出口门同理可知,因为它是一个返回消息,连接从一个生命线到图的框架,箭头连接框架。

图 14: 图 11 的重构,这次使用门

图 14: 图 11 的重构,这次使用门

图 15: 图 12 的重构,这次使用门

图 15: 图 12 的重构,这次使用门

组合碎片(跳转和并行)

在本文前面“基础”的段落中呈现的,我介绍了“变体”,“选择项”,和“循环”的组合碎片。这些三个组合碎片是大多数人将会使用最多的。然而,有二个其他的组合碎片,大量共享的人将会发现有用——跳转和并行。

跳转

跳转组合碎片几乎在每个方面都和选择项组合碎片一致,除了两个例外。首先,跳转的框架namebox的文本“break”代替了“option”。其次, 当一个跳转组合碎片的消息运行时,封闭的交互作用的其他消息将不会执行,因为序列打破了封闭的交互。这样,跳转组合碎片非常象 C++ 或 Java 的编程语言中的break关键字。

图 16: 来自图 8 的序列图片段的重构,片段使用跳转代替变体

图 16: 来自图 8 的序列图片段的重构,片段使用跳转代替变体

跳转最常用来做模型异常处理。图 16 是图 8 的重构,但是这次图16使用跳转组合碎片,因为它把balance < amount的情况作为一个异常对待,而不是一个变体流。要阅读图 16,你从序列的左上角开始,向下读。当序列到达返回值“balance”的时候,它检查看看是否余额比金额更少。如果余额不少于金额,被传递的下一个消息是 addDebitTransaction 消息,而且序列正常继续。然而,在余额比金额更少的情况下,然后序列进入跳转组合碎片,它的消息被传递。一旦跳转组合的消息的已经被传递,序列不发送任何其它消息就退出(举例来说,addDebitTransaction)。

注意有关跳转的一件重要的事是,它们只引起一个封闭交互的序列退出,不必完成图中描述的序列。在这种情况下,跳转组合是变体或者循环的一部分,然后只是变体或循环被退出。

并行

今天的现代计算机系统在复杂性和有时执行并发任务方面不断进步。当完成一个复杂任务需要的处理时间比希望的长的时候,一些系统采用并行处理进程的各部分。当创造一个序列图,显示并行处理活动的时候,需要使用并行组合碎片元件。

并行组合碎片使用一个框架来画,你把文本“par”放在框架的 namebox 中。然后你把框架的内容段用虚线分为水平操作元。框架的每个操作元表示一个在并行运行的线程。

图 17: oven 是并行做两个任务的对象实例

图 17: oven 是并行做两个任务的对象实例

图 17 可能没有举例说明做并行活动的对象的最好的计算机系统实例,不过提供了一个容易理解的并行活动序列的例子。序列如这样进行:hungryPerson 传递 cookFood 消息给oven 对象。当oven 对象接收那个消息时,它同时发送两个消息(nukeFood 和 rotateFood)给它本身。这些消息都处理后,hungryPerson 对象从oven 对象返回 yummyFood 。





回页首


总结

序列图是一个用来记录系统需求,和整理系统设计的好图。序列图是如此好用的理由是,因为它按照交互发生的时间顺序,显示了系统中对象间的交互逻辑。





回页首


参考





回页首

脚注

1 在完全建模系统中,对象(类的实例)也将会在系统的类图中建模。

2 当阅读这个序列图时,假定分析师登录进入系统之内。

3 请注意,附着在不同的变体操作元上的、两个或更多的约束条件式的确可能同时是真,但是实际最多只有一个操作元将会在运行时发生(那种情况下变体的“wins”没有按照 UML 标准定义)。

4 虽然操作元看起来非常象公路上的小路,但是我特别不叫它们小路。泳道是在活动图上使用的 UML 符号。请参考The Rational Edge 早期关于 活动图 的文章。

5 通常,附上约束的生命线是拥有包含在约束表达式中的变量的生命线。

6 关于选择项组合碎片,循环组合碎片不需要在它上放置一个约束条件。

7 可能重用任何类型的序列图(举例来说,程序或业务)。我只是发现开发者更喜欢按功能分解他们的图。



UML 基础: 组件图

developerWorks
文档选项
<script language="JavaScript" type="text/javascript"> </script>
将打印机的版面设置成横向打印模式

打印本页

<script language="JavaScript" type="text/javascript"> </script>
将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

Donald Bell, IBM 全球服务, IBM 

2005 年 2 月 15 日

来自 Rational Edge:这篇文章介绍组件图,一个在新的统一建模语言 2.0中规定的结构图。

Illustration 该文是关于在统一建模语言 2.0或UML中使用的基本图的一系列文章中的一部分。在以前关于 UML 类图中,我描述了类图的标记集如何作为所有UML 2结构图的基础。随着UML 2的轨迹,本文介绍组件图。

图的目的

组件图的主要目的是显示系统组件间的结构关系。在 UML 1.1 中,一个组件表现了实施项目,如文件和可运行的程序。不幸地,这与组件这个术语更为普遍的用法、指象COM组件这样的东西相冲突。随着时间的推移及UML的连续版本发布, UML 组件已经失去了最初的绝大部分含义。UML 2 正式改变了组件概念的本质意思;在 UML 2 中,组件被认为是独立的,在一个系统或子系统中的封装单位,提供一个或多个接口。虽然 UML 2 规范没有严格地声明它,但是组件是呈现事物的更大的设计单元,这些事物一般将使用可更换的组件来实现。但是,并不象在 UML 1. x中,现在,组件必须有严格的逻辑,设计时构造。主要思想是,你能容易地在你的设计中重用及/或替换一个不同的组件实现,因为一个组件封装了行为,实现了特定接口。 1

参考 UML 基础系列的其他文章和教程

RSS 订阅 UML 相关文章和教程的 RSS 提要

在以组件为基础的开发(CBD)中,组件图为架构师提供一个开始为解决方案建模的自然形式。组件图允许一个架构师验证系统的必需功能是由组件实现的,这样确保了最终系统将会被接受。

除此之外,组件图对于不同的小组是有用的交流工具。图可以呈现给关键项目发起人及实现人员。通常,当组件图将系统的实现人员连接起来的时候,组件图通常可以使项目发起人感到轻松,因为图展示了对将要被建立的整个系统的早期理解。

开发者发现组件图是有用的,因为组件图给他们提供了将要建立的系统的高层次的架构视图,这将帮助开发者开始建立实现的路标,并决定关于任务分配及(或)增进需求技能。系统管理员发现组件图是有用的,因为他们可以获得将运行于他们系统上的逻辑软件组件的早期视图。虽然系统管理员将无法从图上确定物理设备或物理的可执行程序,但是,他们仍然欢迎组件图,因为它较早地提供了关于组件及其关系的信息(这允许系统管理员轻松地计划后面的工作)。





回页首


符号

在现在,组件图符号集使它成为最容易画的 UML 图之一。图 1 显示了一个使用前 UML 1.4 符号的简单的组件图;这个例子显示两个组件之间的关系:一个使用了Inventory System组件的Order System组件。正如你所能见到的,在UML 1.4 中,用一个大方块,并且在它的左边有两个凸出的小方块,来表示组件。

图 1:这个简单的组件图使用 UML 1.4 符号显示Order System的一般性依赖关系

图 1:这个简单的组件图使用 UML 1.4 符号显示Order System的一般性依赖关系

上述的 UML 1.4 符号在 UML 2 中仍然被支持。然而,UML 1.4 符号集在较大的系统中不能很好地调节。关于这一点的理由是,如同我们在这篇文章的其余部分将会见到一样,UML 2 显著地增强了组件图的符号集。在维持它易于理解的条件下,UML 2 符号能够调节得更好,并且符号集也具有更多的信息。

让我们依照 UML 2 规范一步步建立组件图。





回页首


基础

现在,在 UML 2 中画一个组件很类似于在一个类图上画一个类。事实上,在 UML 2 中,一个组件仅仅是类概念的一个特殊版本。这意味着适用于类分类器的符号规则也适用于组件分类器。(如果你已经读了并理解了我以前的关于大体上的结构图和类图细节的文章 [http:// www. ibm.com/developerworks/cn/rational/rationaledge/content/feb05/bell/index.shtml],你就会很易理解组件图)。

在 UML 2 中,一个组件被画成堆积着可选择小块的一个立着的长方形。UML 2 中,组件的一个高层次的抽象视图,可以用一个长方形建模,包括组件的名字和组件原型的文字和/或图标。组件原型的文本是“«component»”,而组件原型图标是在左边有两个凸出的小长方形的一个大长方形(UML 1.4 中组件的符号元素)。图 2 显示,组件可以用UML 2规范中的三种不同方法表示。

图 2:画组件名字区的不同方法

图 2:画组件名字区的不同方法

当在图上画一个组件时,重要的是,你总要包括组件原型文本(在双重尖括号中的那个component,如图 2 所示)和/或图标。理由呢?在 UML 中,没有任何原型分类器的一个长方形被解释为一个类组件。组件原型和/或图标用来区别作为组件元素的长方形。

为组件提供/要求接口建模

在图 2 中所画的Order组件表现了所有有效的符号元素;然而,一个典型的组件图包括更多的信息。一个组件元素可以在名字区下面附加额外的区。如前面所提到的,一个组件是提供一个或更多公共接口的独立单元。提供的接口代表了组件提供给它的用户/客户的服务的正式契约。图 3 显示了Order组件有第二个区,用来表示Order组件提供和要求的接口。 2

图 3:这里额外的区显示Order组件提供和要求的接口。

图 3:这里额外的区显示Order组件提供和要求的接口。

在图 3 中的Order组件例子中,组件提供了名为 OrderEntry 和 AccountPayable 的接口。此外,组件也要求另外一个组件提供Person接口。 3

组件接口建模的其它方法

UML 2 也引入另外一种方法来显示组件提供并要求的接口。这个方法是建立一个里面有组件名的大长方形,并在长方形的外面放置在 UML 2 规范中称为接口符号的东西。这第二种方法在图 4 中举例说明。

图 4: 一种可选择的方法(与图3相比):使用接口符号显示组件提供/要求的接口

图 4: 一种可选择的方法(与图3相比):使用接口符号显示组件提供/要求的接口

在这第二种方法中,在末端有一个完整的圆周的接口符号代表组件提供的接口 -- “棒棒糖”是这个接口分类器实现关系符号的速记法。在末端只有半个圆的接口(又称插座)符号代表组件要求的接口(在两种情况下,接口的名字被放置在接口符号本身的附近)。即使图 4 看起来与图 3 有很大的不同,但两个图都提供了相同的信息 -- 例如,Order组件提供两个接口:OrderEntry 和 AccountPayable,而且Order组件 要求 Person接口。

组件关系的建模

当表现组件与其他的组件的关系时,棒棒糖和插座符号也必须包括一支依存箭头(如类图中所用的)。在有棒棒糖和插座的组件图上,注意,依存箭从强烈的(要求的)插座引出,并且它的箭头指向供应者的棒棒糖,如图 5 所示。

图 5:显示Order系统组件如何依赖于其他组件的组件图

图 5:显示Order系统组件如何依赖于其他组件的组件图

图 5 显示,Order系统组件依赖于客户资源库和库存系统组件。注意在图 5 中复制出的接口名 CustomerLookup 和 ProductAccessor。 在这个例子中,这看起来可能是不必要的重复,不过符号确实允许在每个依赖于实现差别的组件中有不同的接口(和不同的名字)(举例来说,一个组件提供一个较小的必需的接口子类)。

子系统

在 UML 2 中,子系统分类器是组件分类器的一个特别版本。因为这一点,子系统符号元素象组件符号元素一样继承所有的组件符号集规则。唯一的差别是,一个子系统符号元素由subsystem关键字代替了component,如图 6 所示。

图 6:子系统元素的一个例子

图 6:子系统元素的一个例子

UML 2 规范在如何区别子系统与组件方面相当含糊。从建模的观点,规范并不认为组件与子系统有任何区别。与 UML 1. x 相比较,这个 UML 2 模型歧义是新的。但是有一个理由。在 UML 1. x 中,一个子系统被认为是一个软件包,而且这个软件包符号正对许多 UML 实践者造成困惑;因此,UML 2中把子系统作为特殊的组件,因为这是最多的 UML 1. x 使用者了解它的方式。这一改变确实把模糊引入图中,但是这一模糊更多的是 UML 2 规范中对抗错误的一个现实反射。

到这里,你可能正在抓着头皮并感到疑惑,什么时候该用组件元素,什么时候又该用子系统元素。相当坦率地说,我没有一个直接的答案给你。我可以告诉你,UML 2 规范中说,何时该使用组件或子系统决定于建模者的方法论。我个人很喜欢这个答案,因为它帮助确保UML与方法论相互独立,这在软件开发中将帮助保持它的普遍可使用。





回页首


超越基础

组件图是比较容易理解的图之一,因此没有很多超越基础的内容。然而,有一个方面你可以认为是略微困难的。

显示组件的内部结构

有时候显示组件的内部结构是有意义的。在关于类图的我的前面的文章中,我显示了该如何为类的内部结构建模;这里,当它由其他组件组成的时候,我将会关注如何为组件的内部结构建模。

为了显示组件的内部结构,你只需把组件画得比平常大一些并在名字区内放置内部的部分。图 7 显示Store组件的内部结构。

图 7: 这个组件的内部结构由其他组件组成。

图 7: 这个组件的内部结构由其他组件组成。

使用在图 7 中显示的例子,Store组件提供了 OrderEntry 接口并要求Account接口。Store组件由三个组件组成:Order,Customer和Product组件。注意Store的 OrderEntry 和Account接口符号在组件的边缘上为何有一个方块。这一个方块被称为一个端口。单纯感觉来说,端口提供一种方法,它显示建模组件所 提供/要求 的接口如何与它里面的部分相关联。 4 通过使用端口,我们可以从外部实例中分离出Store组件的内部部件。在图 7 中,对于过程而言,OrderEntry 端口代表Order组件的 OrderEntry 接口。同时,内部的Customer组件要求的Account接口被分配到Store组件的必需的Account端口。通过连接Account端口,Store组件内部部件(例如Customer组件)可以有代表执行端口接口的未知外部实体的本地特征。必需的Account接口将会由Store组件的外部组件实现。 5

在图 7 中,你可能也注意到了,在内部的组件之间的内部连接与图 5 中显示的那些不同。这是因为内部结构的这些描绘事实是嵌套在分类器(在我们的例子中是一个组件)里的协作图,因为协作图显示分类器中的实体或角色。在内部的组件之间建模的关系以 UML 称为的一个组合连接器表示。一个组合连接器绑定一个组件 提供 的接口到另外的一个组件的 必需 接口。组合连接器用紧紧相连的棒棒糖和插座符号表示。以这种方式画这些组合连接器使棒棒糖和插座成为很容易理解的符号。





回页首


结论

组件图经常是一个架构师在项目的初期就建立的非常重要的图。然而,组件图的有用性跨越了系统的寿命。组件图是无价的,因为它们模型化和文档化了一个系统的架构。因为组件图文档化了系统的架构,开发者和系统可能的系统管理员会发现这一工作的关键产品有助于他们理解系统。

组件图也视为软件系统配置图的输入,这将会是本系列后面的文章主题。





回页首

脚注

1在UML1.x 中称为组件的实际项目,在 UML 2 中称为产物。一个产物是一个物理单位,象一个文件,可运行的程序,脚本,数据库等等。只有一种产物依赖于实际的节点;类和组件没有“位置”。然而,一个产物可能显示组件和其他的分类器(例如类)。一个单一的组件可能通过多重产物显示,它们可能是在相同的或不同的节点上,因此,一个单一的组件可以间接地在多重节点上被实现。

2即使组件是独立的单元,它们仍然可能依赖于其他组件提供的服务。由于这一点,文档化一个组件的必需接口是很有用的。

3图3并不显示Order组件完整的上下文。在一个真实的模型中,OrderEntry,AccountPayable 和Person接口会呈现在系统的模型中。

4事实上,端口适用于任何类型的分类器(例如,一个类或者你的模型中可能会有的其他分类器)。为了使本文简洁,我在组件分类器及它们的使用中提及端口。

5一般来说,当你画一个端口和一个接口之间的依存关系时,依赖方(要求)的接口将会在运行时间内处理所有的处理逻辑。然而,这并不是一种硬性的规定 -- 对于周围的组件(举例来说,我们例子中的Store组件),使用自己的进程逻辑,而不是仅把进程委托给依赖接口,是完全可以接受的。



绘制整洁的 UML 图

明晰才能被人采纳

developerWorks
文档选项
<script language="JavaScript" type="text/javascript"> </script>
将打印机的版面设置成横向打印模式

打印本页

<script language="JavaScript" type="text/javascript"> </script>
将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

Scott W. Ambler (scott.ambler@ronin-intl.com), 总裁, Ronin International

2000 年 11 月 27 日

不管您喜欢与否,诸如统一建模语言 (UML)类模型和用例模型这样的软件图往往是根据它们的外观来判定其好坏的。看上去整洁的图比看上去杂乱的图更容易受到读者 -- 常常是您的用户或高级经理 -- 的青睐。

我很愿意描述几个重要的经验法则,这些法则将使您比其他建模同仁做得更好。这些虽然简单但很关键的建议主要集中在如何安排组成软件图(包括UML类模型、用例模型,甚至持久模型)的那些框和线条,并因此适用于所有种类的图。

要绘制一个外观整洁的图,您应该避免:

让我们从一个示例开始。在图 1 和 2中,您可以看到两个用两种不同风格绘制的图。第一个复杂,没有章法,而第二个简单,组织良好(虽然有些乏味)。您认为哪个设计更好呢?大多数人都会赞成第二个看上去更好一些,因为虽然这两种设计在功能上是相等的,但第二个的安排更整洁。


图 1. “杂乱”的图
杂乱的图

图 2. “整洁”的图
整洁的图

避免大小不一的框
如何对图 1加以改进呢?首先,确保所有框的大小都一样。大框看上去比小框更重要一些,如果这是您尝试表达的,那么这样做没错--但如果让我选的话,我宁愿将所有框保持相同的大小。这种方法最适合于“UML用例”图,因为其中的所有用例框和参与者符号可以很方便地统一成一样,此外还适用于“UML协作图”、“UML 序列图”和“UML用户界面流程图”。对于框中包含的信息量不同的图,例如“UML类图”(其中个别类有数量不等的属性和操作),或者“UML状态图表图”和“持久”(数据)模型,那就有一些困难了。

避免对角线
图 2 与图 1 的另一个不同之处在于它没有任何对角线。我是通过重新安排框来消除对角线的,就好象它们在一个网格上,使互连的框或者在垂直方向上分离,或者在水平方向上分离。从视觉上说,大多数人对直线更感兴趣。

避免交叉线
在图 1中,有两条线相互交叉,我的一个常规经验法则是应该尽量减少图中交叉线的数量。通过将一些框移到旁边,我在短时间内就可以避免使两条线交叉。可惜,不是总能这样幸运-- 您无法总能避免交叉线。在图 3 中,我想将 5个框全部连接起来,但如果不使至少两条线相交就无法做到这一点。您可以看到,我没有其它方法将框3 和 5连接起来。在不得不交叉线时,我会用适用于电路图的标准来标记:一条线“跳过”另一条,如图4所示。跳过的好处是它很清楚地表明线只是在图上交叉,而不以任何方式连接。


图 3. 如何在不交叉线的情况下连接 3 和 5?
如果在不交叉线的情况下连接 3 和 5?

图 4. 一条线“跳”过另一条
一条线跳过另一条

避免曲线
您可以在图 5 中看出,我对图 4做了更进一步的改进:除去了曲线。人们喜欢看到垂直或水平的直线。这次我又假装是在网格上绘制图(实际上这是许多计算机辅助系统工程(CASE)工具的内置特性),然后只需要象在网格上那样绘制出框和线条。


图 5. 图 4 的更整洁版本
图 4 的更整洁版本

避免混乱或复杂的图
显示太多细节或者外观很混乱的图看上去不太好。最好能够有几张显示各种程度的细节的图,而非一张显示所有事物的复杂的图。这就是为什么UML拥有几种图的原因之一:一个软件是如此复杂,以至于我们无法在单一图上对其所有方面建模。而且,UML 允许将包添加到图中(下星期的技巧主题)。

另一个相关的注意事项是对屏幕或页面区域的使用。在我看来,一张占据几页的图比将所有内容蜷缩在一起,使它能在一页上打印出的图要好得多。您应该给图留出足够的空间,使它易于理解。

避免在图的美化上浪费太多时间
尽管这些经验法则非常有效,但无休止地调整图的外观总是会增加额外的建模时间。解决这个问题的一个方法是尝试使图的外观保持在大致良好的水平上--您在使用图时,不需要它非常完美。一旦确信图按照您所需的方式对应用程序建模,就可以开始移动框以避免交叉线,增进其可理解性。

您的主要目标是对系统建模,而不是绘制漂亮的图。有必要指出这些重要的经验法则也可以被用来美化低劣的设计。例如,我可以从图2 开始,将它重排成图 1,以使设计看上去比实际的更为复杂 --可能使得高级管理人员相信我需要更多时间或资源才能完成工作,或者引导他们避开我不是特别喜欢的备选设计。假设您的动机随情形而改变,我希望您所处的情形是健康的,您所考虑的最重要的问题是使了不起的设计看上去更引人入胜,而不是在办公室权术中求生存。


利用 UML 进行实体关系建模

developerWorks
文档选项
<script language="JavaScript" type="text/javascript"> </script>
将打印机的版面设置成横向打印模式

打印本页
<script language="JavaScript" type="text/javascript"> </script>
将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 初级

Davor Gornik, 高级工程师

2004 年 11 月 01 日

软件行业中最常被误解的一个术语实际上是我们非常熟悉的一个:实体关系(ER)。这是因为我们经常缺少一种能被开发团队的所有成员理解的共同定义。我们假定团队的每个成员都对与 ER 和 ER 建模相关的方法学、语法和机制(mechanics)有着同样清楚的理解。

实体关系建模

软件行业中最常被误解的一个术语实际上是我们非常熟悉的一个:实体关系(ER)。这是因为我们经常缺少一种能被开发团队的所有成员理解的共同定义。我们假定团队的每个成员都对与 ER 和 ER 建模相关的方法学、语法和机制(mechanics)有着同样清楚的理解。

ER 建模本身定义了在基于信息的系统的分析和设计中用到的方法。数据库设计者通常使用该方法来收集需求,并定义数据库系统的构架。该方法的输出是实体类型、关系类型和约束条件的清单。

不幸的是,ER 建模没有为 ER 图的表示定义图解语法。数据库团队经常单独使用表示法,并且将 ER 建模限制在关系数据库设计的范围内。我们需要一种能让整个系统开发团队的成员获得更广泛理解的表示法。

统一建模语言(UML)是一种分析人员和软件开发人员广泛使用的语言,特别适合 ER 图的图形化表示。通过使用 UML,开发团队受益匪浅,这些获益包括团队成员间的交流更加简单,由于该语言是基于元模型的因而更容易与知识库集成,标准化输入/输出格式(XMI)的使用,应用建模和数据建模的普遍使用,从分析到实施再到部署的统一表示,以及规格说明书的完整性。

本白皮书定义了 ER 建模的核心概念,并解释了开发团队如何能够利用 UML 开发 ER 模型。





回页首


ER 建模的核心要素

ER 建模基于工件,可以是物理工件(比如 Product 或 Employee)的表示或者工件(比如 Order 或 Delivery)之间事务的表示。每个工件都包含关于自身的信息。ER 建模还专注于工件间的关系。这些关系可以是二元的(连接两个工件),也可以是三元的(在几个工件之间)。

ER 建模的四个必要元素是:

  1. 实体类型
  2. 属性
  3. 关系类型
  4. 关系属性

实体类型

实体类型是具有相同的结构并在企业内部独立存在的一组工件。Employees 或 Products 就是实体类型的例子。

工件的一次出现就是一个实体。虽然实体类型描述了结构,但是实体本身标识了单个实例以及该实例的所有数据。Employee Joe Ward 就是 Employees 实体的一个例子。


图 1 实体类型 Employees 和实体 Employees Joe Ward
图 1 实体类型 Employees 和实体 Employees Joe Ward

属性

实体类型的结构是用属性定义的。属性可看作实体类型的特征。Employee 的属性可能有姓名、住址、社会安全号码、出生日期、参加工作日期以及职务等。

实体通过属性值来互相区分。由于实体的属性可能有相同的值,如果这样我们就不能区分某些特定的实体。因此,我们必须保证特定实体的属性值与其他实体的属性值不同。各个 Employee 都有一个唯一的姓名和社会安全号码属性组合。

Employee 的属性值的一个例子是:Joe Ward,地址为 34 Main Road, Redmond, WA, 98053,社会安全号码为 555-32-2222,出生于 1971 年 9 月 7 日,2001 年十月 1 日加入公司,是家电服务工程师。


图 2 实体类型 Employee 的属性以及实体 Employee Joe Ward 的属性值
图 2 实体类型 Employee 的属性以及实体 Employee Joe Ward 的属性值

关系类型

实体类型描述了独立工件,而关系类型描述了实体类型间有意义的关联。更准确的说,关系类型描述了参与该关系的实体类型的实体可以构建一个有意义的关联。实体间实际发生的关联被称为一种关系。

有一点我们必须理解:尽管我们已经定义了一种关系类型,但是这并不意味着每一对实体都构建了一种关系。关系类型规定了发生关系的类型。

关系类型的一个例子是 Employee 拥有 Product。在该例中,关系类型是 Owns。关系本身是:Employee Joe Ward 拥有一种产品--一部编号 320 TS 03880 的黄色电话机。


图 3 雇员 Joe Ward 和序号 320 TS 03880 的产品之间的拥有关系类型和拥有关系
图 3 雇员 Joe Ward 和序号 320 TS 03880 的产品之间的拥有关系类型和拥有关系

也有可能有一位名叫 Martin Weber 的雇员就没有拥有电话机。

关系类型的属性

关系类型还可以包含属性。比如,Employee 和 Product 间的关系类型 Services 可以包含属性 Date 和 Status,标识了服务的日期以及服务之后产品的状态。

当在具体发生的服务中实现关系时,该关系的属性值就被设置。关系的含义可能是:Joe Ward 在 2002 年 7 月 3 日为序号是 0462834 DF 4的黑色饮水机提供维修服务,并且使其建立了良好的工作状态。


图 4 services 关系类型的属性,以及 Joe Ward 为序号 0462834 DF 4的产品提供服务的关系的属性
图 4 services 关系类型的属性,以及 Joe Ward 为序号 0462834 DF 4的产品提供服务的关系的属性

ER 建模中的简单约束

ER 模型中的实体、关系和属性建立了一些定义企业结构的限制。该结构受被称为"约束"的规则所限制。比如,一个雇员不可能与 100 多位客户打交道。或者说,每个雇员必须与某一个恰当的部门关联。

基数(Cardinality)

每个指定的关系类型都定义了在所有参与实体之间建立关系的可能性。大多数情况下,这不是必需的。比如,并非所有 Employee 都拥有全部 Product。

关系是双向的,连接了两种实体类型(Employees 和 Products)或者扮演两个不同角色(作为经理的 Employee 和作为下属的 Employee)的同一实体类型。关系还可以是多向的,连接两个以上的实体类型。连接一个雇员、一个客户和两部话机的一次电话呼叫就是多向关系的一个例子。不管是哪种情况,每个实体类型都指定了针对该关系类型的基数。

通过每个实体最大的关系数目指定了最简单的基数。如果只有一个部门参与和一个雇员关联的关系,那么我们在连接器上写一个 1。这意味着 Joe Ward 必须与一个并且只能是一个部门关联。

其他的基数还有 Not Specified 或 Specified By a Variable。Not Specified 基数没有限制。基数 Specified By a Variable (大部分为M 或N)同样没有基数限制。

当一个关系中的参与实体的上限和下限不同时,我们为上限和下限指定一对值,用括号括起来,之间用逗号分开,如(M,N)。根据上限的不同,可选的关系可以用(0,1)或(0,N)表示。

比如,足球队和球员的关系为(11,18)。实体 Redmond Lions 足球队与实体类型球员之间建立了一种关系,它由 Joe Coplen、David Archer、John Good、Kevin Hale、Ivan Komashinsky、Steven Cooper、Andrew Bliven、Art Lounsbery、Chad Beery、Randall DuBois、Ron Baghai、Lance Delo、Tito Magobet、Curtis Hrischuk 和 Ian Leslie 组成。


图 5 足球队和球员之间的关系规定这种关系只有在球队球员在 11 到 18 之间时才有效
图 5 足球队和球员之间的关系规定这种关系只有在球队球员在 11 到 18 之间时才有效

图 6 有效的关系是 Redmond Lions 足球俱乐部和 15 名球员之间的队员
图 6 有效的关系是 Redmond Lions 足球俱乐部和 15 名球员之间的队员

足球比赛中经常有队员由于不当的行为或者犯规而领到黄牌或红牌。它们用实体类型 Cards 来表示。Cards 实体类型与基数为(0,N)的球员构建了一种 Received 关系。这意味着球员 David Archer 可能与三个 Card 实体有着 Received 关系,而 Lance Delo 却一个也没有。



图 7 实体类型球员可能收到 0 个或者任意数目的 Card 实体类型:David Archer 收到 3 张牌,而 Lance Delo 一张牌也没有收到。

依赖关系

依赖关系指的如果在一个关系中指定的其中一个实体不存在,那么另一个实体也没有存在的意义。当依赖型实体(子类型)的各个实体依赖于超类型中对应父实体存在时,则一个实体类型依赖于另一个实体类型。

必须通过显式地定义一个下限不为 0 的基数,或者定义个一个不为 0 的固定基数,来指定一个强制的依赖关系。实体类型结婚证就是两个实体间依赖关系的一个例子,其中结婚证依赖于实体类型人。关系是结婚,并且结婚证和两个人之间具有固定的依赖关系。

比如,实体结婚证 352647003 与实体 Joe Ward 和 Melinda Bell 具有固定的依赖关系。这意味着如果 Melinda Bell 或 Joe Ward 脱离该关系,那么至少从数据的角度来说,结婚证352647003就已经失效。


图 8 建立在实体类型人之上的依赖性实体类型结婚证,以及实体结婚证 352647003 与 Melinda Bell 和 Joe Ward 间的依赖关系
图 8 建立在实体类型人之上的依赖性实体类型结婚证,以及实体结婚证 352647003 与 Melinda Bell 和 Joe Ward 间的依赖关系

特化和泛化

核心 ER 模型只定义了实体类型间的基本关系。虽然利用基本的实体和关系就可以很容易地表示商业机构中的大多数简单数据结构,但是技术应用要求基于实体类型间的相似点和不同点的更复杂的结构。

特化和泛化

特化和泛化的目的在于重用与实体类型关联的属性和行为。

特化用于定义代表一个大型实体类型的一个特定部分的实体类型。特化后的实体类型从父实体类型继承了结构和行为,比如业务规则。然而,虽然特化后的实体类型扩展了父结构或类型,但是这决不是说它小于父类型。

比如,Employee 是实体类型 Person 的一个特化,它需要实体类型 Person 的所有属性和关系。另外还有一种叫做 Customer 的实体类型,它也是实体类型 Person 的一个特化。这两种实体类型都具有 Person 的属性,它们被看作 Employee 或 Customer 的属性。因此在我们看 Customer 时,看到的是在实体类型 Person 和实体类型 Customer 中指定的所有属性。

泛化是正好相反的工作流。泛化实体类型(或者父类型)代表所有子类型的共同结构和行为,并且包含了来自子实体类型的所有共同属性。子实体类型具有父属性的所有内容,并且还拥有自己的属性。

泛化过程找出共同结构并在父实体类型中抽象出来。父实体类型通常在比较实体类型和简化模型时的重构阶段被找到。

虽然泛化仅利用面向对象或者对象关系数据库就可以直接实现,但是泛化也可以通过使用外键的任何关系数据库直接实现。


图 9 实体类型 Customer 和实体类型 Employee 被泛化为人实体类型;反向过程就是特化
图 9 实体类型 Customer 和实体类型 Employee 被泛化为人实体类型;反向过程就是特化

分类(Categorization)

特化是在实体上完成的,而分类则定义了关系类型上的约束条件。大多数情况下,分类是排他性的,这意味着根据实体状态的不同,一个实体要么参与关系 A 要么参与关系 B。该状态可能是一个属性值(另一种关系的存在),或是某些外部状态。

分类不改变实体的属性。它需要数据访问和操纵,来考虑分类中指定的约束条件。

交通工具就是分类的一个好例子。根据交通工具种类的不同,我们需要构建不同的关系。对于卡车,我们需要货物信息,而对于公共汽车,我们需要乘客姓名。这些信息将被用于不同的关系中,以便为这些关系提供有意义的上下文。


图 10 根据范畴的不同,卡车和公共汽车中的分类与实体类型 Cargo 和 Person发生关系
图 10 根据范畴的不同,卡车和公共汽车中的分类与实体类型 Cargo 和 Person发生关系




回页首


ER 方法学的表示法

目前,ER 建模中使用了数种表示法,包括陈氏 ER、Barker ER Information Engineering (IE)和 IDEF1X,其中大部分还包含关系表示法。

对象角色建模(ORM)表示法也是 ER 方法学的一员。它能表达非常精确和完整的业务规则,并且可以用图形阐述约束条件。不幸的是,这种详细程度需要具有丰富细节的大量图表。与 ORM 相关的问题是我们是否需要 ER 模型级的表示法精确度。ORM 表示法还很复杂,并且与其他表示法完全不同,因而那些没有使用 ORM 的项目组成员很难理解它。

UML 是一种表示法语言,最初用于软件设计,目前软件设计已经扩展到业务和数据库设计。UML 包括从分析到实施再到部署指定任何事项所必需的元素和图表。通过使用几种图表和数十种元素,UML 还能表达不同程度的系统抽象。这是一项非常独特的功能。但是,我们不需要知道 UML 的所有细节或者成功利用 UML 进行 ER 建模所需的所有视图和表示。

为了更好理解 UML 在 ER 方法学中扮演的角色,我们将描述 UML 如何处理前面已经提到过的 ER 建模的核心元素。

UML 中的实体类型

如前所述,实体类型标识了具有同样结构的一系列工件。实体类型是一幅蓝图,根据它能生成只能通过身份和状态互相区别的任意数目的工件。

UML 中的相应元素是类。根据定义,类能够隐藏内容,而实体具有可访问接口。这看上去互相矛盾,但是实际上并非如此。UML 允许类利用公共属性使结构公共化。

类一般用矩形表示,该矩形最多可分为三个部分:

第一部分包括类的原型和名称。原型指的是 UML 中为了强化共同特征而进一步进行的元素分类。比如,所有可能带有遗留原型的遗留类,可能将立即将遗留原型划分为不可修改的一类。虽然类本身是类型的一种表示,但是我们用原型<<实体>>来划分类型(<<…>>是用于指定原型的语法)。

第二部分包含具有类型和可见性的属性。它还可以包含属性的其他细节,比如初始值和原型。第二部分在缩略图中可以省略。

第三部分是为类的行为保留的。由于实体类型不需要行为,所以我们就略过该部分。

根据抽象级别的不同,类可以用一个、两个或三个部分显式。

实体是实体类型的一个实例。在 UML 中,对象是类的实例。这意味着实体本身与对象相对应。

对象的表示来源于类的表示。最显著的区别在于对象的名称有下划线,并且只有一个或两个部分。

第一个部分包含以冒号隔开的可选的原型、对象名称,以及派生类的名称,之间用冒号隔开。至少必须指定其中一个名称。第二部分包含相关属性以及它们的值。

表示对象的最好的方法就是只使用一个部分,并将标识符 指定为对象的名称。


图 11 实体类型 Employees 和实体 553-32-2222 在 UML 中被显示为类和对象
图 11 实体类型 Employees 和实体 553-32-2222 在 UML 中被显示为类和对象

UML 中的属性

实体的相关状态和信息被作为对象的属性存放。实体类型的属性对于其他实体类型必须是可见的或者公开的。在类规格说明书中属性是按照可见性、名称和类型指定的。属性的类型为分析类型。它在设计阶段可能发生变更。

属性是在类的第二部分指定的。它们包含对于实体总是"+"(公开)的可见性规格说明书。名称与类型之间用冒号隔开。


图 12 具有公开属性(社会安全号码、姓名、地址、出生日期、加入日期和职位)的实体类型 Employees
图 12 具有公开属性(社会安全号码、姓名、地址、出生日期、加入日期和职位)的实体类型 Employees

UML 中的关系类型

在 UML 中类之间的关系是为类型而非实例指定的。关系双方上的数字指定了基数:参与该关系的可能实例的数量。

关系的名称是直接在关系行中指定的,用于标识该关系。它有助于读者理解存在这种关系的原因。如果没有为一个关系指定名称,那么角色名将用于帮助读者理解该关系。下图可以这样理解:Products 被 Employees 拥有或者 Employees 是 Products 的拥有者。该关系描述中单词的单复数通过基数确定。


图 13 实体类型 Employee 和 Product 之间的关系
图 13 实体类型 Employee 和 Product 之间的关系

实体类型中用到的数据类型没有被标准化。用户可以使用所需要的任何数据类型。创建具有所有数据类型的术语表是一个不错的做法,因为它可以在公司内多个设计者和项目中实现标准化和可理解性。

UML 中关系类型的属性

关系可以具有属性,这些属性显示在 UML 关联类中。关联类显示在一个矩形中,并且包含该关系的公共属性清单。关联类利用虚线与关系连接。不需要原型来解释类用法和为该类分类,因为附件已经对它做了定义。

关联类中的属性是用可见性、名称和类型指定的。

尽管看上去关联类、附件和关系是独立元素,但是它们实际上代表了同样的元素。关系和关联类的名称必须相关。


图 14 关联类中指定了关系类型 Services 的属性
图 14 关联类中指定了关系类型 Services 的属性

UML 中的简单约束条件

基数

UML 定义了一种一致的方法来指定基数。它总是被关系双方上的数字指定。可能的定义包括用于一定数量(也可能是不限量的)实例的指定基数的单个数字,以及一对规定了基数的范围的以".."隔开的数字。用于无限基数的符号是"*",它可以单独使用标识可选的无限关系,也可以与另一个很低的值结合使用,来指定强制关系(如"1..*")。基数的下限和上限值可以是任意正数或者"*",但是第一个数字必须小于或等于第二个数字。


图 15 实体类型 SoccerTeam 定义了与 11 到 18 个球员的实体类型 Player 的 plays 关系
图 15 实体类型 SoccerTeam 定义了与 11 到 18 个球员的实体类型 Player 的 plays 关系

依赖关系

UML 可以分辨两种形式的实体类型间的依赖关系。聚合是需要依赖性实体类型的两个实体类型之间的一种依赖关系。UML 中聚合的语法是在聚合方用空心菱形表示。同一方还有一个值为 1 的强制基数,它可以省略。


图 16 每个结婚证实体类型都依赖于扮演新郎和新娘角色的两个人
图 16 每个结婚证实体类型都依赖于扮演新郎和新娘角色的两个人

当聚合不是对于所有依赖关系都唯一,并且不是所有依赖性实例都必须与同一个实体相关时,就可以使用聚合。当聚合是所有依赖关系的唯一实体时,UML 指定了一种叫做组合的强依赖关系。组合被表示为聚合方上的一个实心菱形。当聚合包含下级实体类型时会使用这种关系。


图 17 实体类型 OrderPositions 完全由组合指定的实体类型 Orders 定义
图 17 实体类型 OrderPositions 完全由组合指定的实体类型 Orders 定义

基数可以与聚合和组合一起使用,以便定义这些关系上的约束条件。

特化和泛化

实体类型的相同点和不同点分析是必不可少的一部分。特化降低了风险,并通过从父方那里继承需求从而减少了需求匮乏。泛化简化了系统中实体类型的模型和实现过程。

在 UML 中泛化是在关系的父方用空箭头表示的。泛化不是两个实体类型间的一种关系。它是从特定实体类型到一般实体类型的派生。在泛化关系上不允许有基数。

父实体类型的所有属性和关系被特化后的实体类型继承。到其他实体类型的属性和关系不能从特化实体类型中删除。


图 18 泛化将实体类型 Employees 和 Customers 定义为继承了 Persons 属性的实体类型 Persons
图 18 泛化将实体类型 Employees 和 Customers 定义为继承了 Persons 属性的实体类型 Persons

分类

同一实体类型的范畴规定了实体如何根据它们的特征(比如所有员工都缺勤)互相相关。分类的语法使用了在 OCL(对象约束语言)中指定的与该关系连接的约束条件。

约束条件旨在指定动态行为。当约束条件被评估为有效时,这些关系也是有效的。

通常约束条件是互相排斥的,可以利用互斥关系之间的约束条件{xor}为约束条件建模。


图 19 实体类型的分类定义了关系类型的标准--Cargo 对于卡车类型的 Vehicle 很重要;Person 可以利用"公共汽车"类型的 Vehicle 被运输
图 19 实体类型的分类定义了关系类型的标准--Cargo 对于卡车类型的 Vehicle 很重要;Person 可以利用"公共汽车"类型的 Vehicle 被运输




回页首


结束语

与一般的观点不同,ER 方法学并不仅限于关系数据库的开发。笔者不得不同意在大多数情况下,设计流程的输出会在关系数据库中实现;然而,这并不是它的一个条件。ER 方法学的焦点在于基于工件的设计。它输出的是互相有关系的工件(实体类型),以及澄清了实体类型和关系的约束条件。该输出用于创建具有额外技术相关约束条件的关系模型。

ER 方法学用于工件驱动系统的分析和设计。它可用于概念和逻辑上的建模,但本意并非是为了物理设计。ER 方法学只描述了工件的静态视图,而没有描述系统的动态。

为了创建一个平滑的开发流程,开发团队的所有成员需要"操同一种语言",这一点很重要。这是因为对信息的曲解可能导致延误,造成意想不到的错误,并降低团队成员的总体效率。UML 通过提供一种很容易被系统开发团队所有成员理解的标准化语言消除了这些担忧。

  



  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值