UML类图中的关联、聚合和组合李云Email:***********************Blog: 摘要本文介绍了UML关联的三种形式,此外,通过给出例子和相应的程序源代码帮助读者加深理解。
关键词UML 关联聚合组合缩略语UML Unifed Modeling Language 统一建模语言参考资料《OMG UML Superstructure version 2.2》类图中的关联关联1 类图中的关联(association,请参见Superstructure的7.3.3节)表示两个或多个类实例之间所存在的一种语义关系(sematic relationship)。
一个关联至少有两个用属性(property,请参见Superstructure 的7.3.44节)表达的终端(end)。
一个关联关系表明了多个所关联类实例(instance)之间的连接(link),也就是说关联是连接的集合。
一个连接是一个包含两个关联终端的值的元组,每一个关联终端的值表示一个末端类型的实例。
图1中,连接类Car和类Window的直线就表示一个关联关系,这个关联关系只有一个连接,因为只有两个类。
连接的两个末端分别是car_和windows_,car_是终端类Car 的实例(名),而windows_是终端类型Window的实例(名)。
在1.3节讨论关联的元数时,我们会进一步讨论连接与关联的关系。
一个关联可以包含多个终端(或说多个类),且关联终端可以是相同的类型(或相同的类)。
图 1关联在我们的语言中的表现形式是什么样子的呢?下面先看看用Visual Paradigm for UML生成图1中的C++代码是怎么样的,在Visual Paradigm for UML中选择相应的C++代码生成菜单,如图2所示。
图 2此时,将出现如图3所示的对话框,选择所需生成代码的元素和被生成代码的存放路径后,点击“Generate”按钮。
之后,在相应的目录中将生成四个文件,分别是Car.h、Car.cpp、Window.h 和Window.cpp。
图 3为了看一看所生成的代码中关联是如何表达的,我们需要查看一下Car.h和Window.h。
其代码如图4所示。
从图4中可以看出关联关系在类中表现为一个成员变量或说是属性。
Car.h00001: #include <string>00002: #include <vector>00003: #include <exception>00004: using namespace std;00005:00006: #ifndef __Car_h__00007: #define __Car_h__00008:00009: // #include "Window.h"00010:00011: class Window;00012: class Car;00013:00014: class Car00015: {00016: private: Window* windows_;00017: };00018:00019: #endifWindow.h00001: #include <string>00002: #include <vector>00003: #include <exception>00004: using namespace std;00005:00006: #ifndef __Window_h__00007: #define __Window_h__00008:00009: // #include "Car.h"00010:00011: class Car;00012: class Window;00013:00014: class Window00015: {00016: private: Car* car_;00017: };0001800019: #endif图 41.1 关联的可导航性可导航性(navigability)表示一个关联中连接的一端在运行时是否能被另一端有效的存取。
或者通俗的说可导航性的意思就是一个类能否通过所关联的类实例来调用类的方法。
对于图1,如果没有指明可导航性,则默认是双向都可导航的,这也是为什么生成的代码会互相拥有一个对方类型的指针变量的原因。
对于图4,从导航性角度来说,由于类Car和类Window之间的关系是相互可导航的,所以类Car可以通过windows_变量调用类Window的方法(或说是函数)。
反之,类Window 也可以通过car_变量来调用类Car的方法。
回到图1中的类图,我们看一看是不是有些东西可以更加的精确。
比如,车(用类Car表示)可能需要调用窗户(用类Window表示)的成员函数以实现窗户的开关,但窗户却不需要调用车的任何函数去实现特定的功能,从UML角度来说,从类Car到类Window是可导航的,但从类Window 不需要导航到类Car。
为此,我们将图1修订成图5。
在1.2节讨论关联的多重性时,我们看一看一个不可导航的关联所生成的代码与可导航的关联有什么不同。
图 51.2 关联的多重性前面说到关联的终端是类的实例,但实例的个数可能是需要指定的,即可以根据应用的需要进行指定。
比如,通常情况下,一辆车有四个窗户,即窗户可以有四个实例;而一个窗户只能是属于一辆车。
这在UML中如何在关联关系上进行表达呢?在UML中对于关联,我们可以用多重性(multiplicity)来限定一个末端(或属性)的数量。
多重性是采用如下的格式来表示的:<lower-bound> ‘..’ <upper-bound>其中,<lower-bound>和<upper-bound>都是一个数字,分别表示实例个数的下限和上限。
此外,“*”表示任意多个,还有就是当上限和下限值相同时,即必须是一个固定值,我们可以采用简写形式。
比如,对于“1..1”我们可以简写成“1”,对于“*..*”我们可以简写成“*”。
加入多重性后的图如图6所示,在后面谈到聚合和组合时,我们会使该图更加的精确。
图6所生成的C++代码列出于图7中。
图 6Car.h00001: #include <string>00002: #include <vector>00003: #include <exception>00004: using namespace std;00005:00006: #ifndef __Car_h__00007: #define __Car_h__00008:00009: #include "Window.h"00010:00011: class Window;00012: class Car;00013:00014: class Car00015: {00016: private: std::vector<Window*> windows_;00017: };00018:00019: #endifWindow.h00001: #include <string>00002: #include <vector>00003: #include <exception>00004: using namespace std;00005:00006: #ifndef __Window_h__00007: #define __Window_h__00008:00009: class Window;00010:00011: class Window00012: {00013: };00014:00015: #endif图7图7与图4代码的区别有两处:由于类Window到类Car不具导航性,因此,在类Window中不再有成员变量car_。
由于在图6中我们增加了多重性,因此,图7中类Car的变量windows_从类型Window*变成了std::vector<Window*>,即从一个指针变成了一个标量。
在Visual Paradigm for UML 中我们可以设置对于多重性的实现是采用STL(Standard Template Library)中的vector 或还是C中的数组。
回到图3你可以看出对于“Association Implementation”我们选择的是vector。
1.3 关联的元数关联中类的个数表示关联的元数(N-ary),比如图1中所示的关联只有两个类,因此我们说它是二元关联(binary association)。
图8是从UML规范中提取出来的一个包含三元关联(ternary association)的类图。
图 8与二元关联所不同的是,三元及以上关联需采用一个菱形来连接所有的关联类。
前面我们谈到了关联与连接的关系,对于图 8中的三元关联,其中存在三个连接,即每二个关联类组成一个连接。
我们说这一个三元关联是由三个连接组成的。
此外,图 8中还存在一个二元关联,而且这一二元关联边上还有一个实心的小三角形。
这一小三角形在图中的作用是用于指示关联关系的阅读顺序。
比如,对于图中的二元关联,我们应当这么读“Player 在year_年参加过比赛”(对应的英文是:Player PlayedInYear year_)。
1.4 关联的可见性关联的可见性(visibility ,请参见Superstructure 的7.3.55节)是指一个类所拥有的关联对于这一类的外部是否可见。
可见性分为私有(private )、保护(protected )、包(package )和公共(public )四种。
各种可见性在UML 中的表示方法如下:“+”表示public ; “-”表示private ; “#”表示protected ; “~”表示package ; 从C++的角度来看,我们对于private 、protected 和public 都已非常熟悉,至于package ,它可以被理解为命名空间(namespace )。
采用Visual Paradigm for UML 我们可以表示出关联的可见性。
可见性被放在关联终端的属性名之前,在图 1中加入关联的可见性后就变成了图 9。
在图9中你可以看到我们将两个关联末端的可见性都设置成了private ,也就是说,windows_对于类Car 的外部是不可见的,而car_对于类Window 的外部也是不可见的。
图 91.5 关联的属性字符串属性字符串(property string )是采用花括号将其括起来,这些属性字符串可以应用于关联的终端。