
4.4 关联关系
对象之间也需要定义通信手段,UML规范中对象之间的通信手段就称为关系。类图中的关联定义了对象之间的关系准则,在应用程序创建和使用关系时,关联提供了维护关系完整性的规则。类关系的强弱基于该关系所涉及的各类间彼此的依赖程度。彼此相互依赖性较强的两个类称为紧密耦合。在这种情况下,一个类的改变极可能影响到另一个类。紧密耦合通常是一个坏事。
4.4.1 二元关联
关联意味着类实际上以属性的形式包含对其他类的一个或多个对象的引用。确定了参与关联的类之后,就可以对关联进行建模了。只有两个类参与的关联可以称为二元关联;多于两个类参与的关联,即为n元关联。在类图中二元关联定义了两个类的对象之间的关系准则,关联定义了什么是允许的,什么是不允许的。如果两个类在类图中具有关联关系,那么在对象图中这两个类的相应对象所具有的关系被称为链。关联描述的是规则,而链描述的是事实。如下图所示演示了Person类和Car类之间的关联关系。Person类定义了人对象及其功能,Car类则定义了小汽车对象及其功能。两者间的关联是一种单一类型的关系,存在于两者的对象之间,解释了这些对象需要通信的原因。

一个完整的关联包括类之间关联关系的直线和两个关联端点。如下图所示演示了关联的组成。其中直线以及关联名称定义了该关系的标志和目的,关联端点定义了参与关联的对象所应遵循的规则。在UML规范中关联端点是一个元类,它拥有自己的属性,例如多重性、约束、角色等。

1.关联的名称
关联的名称表达了关联的内容,含义确切的名称使人更容易理解。如果名称含糊不清,就容易引起误解和争论,导致建模开销的增加和建模效率的降低。一般情况下,使用一个动词或者动词短语命名关联关系。下图显示的是同一关联的两个不同的名称,即“holds”和“is holded by”。

在命名关联关系时存在如下假定:如果要从相反的方向理解该关联,只需将关联名称的意义反过来理解。例如,上图中的关联可以理解为“Person对象拥有Car对象”,如果从相反的方向理解也是可以的,即“Car对象被Person对象拥有”。因而,对于上图中的关联,只建立其中一个模型即可。
通常情况下,人们喜欢从左到右地阅读,所以当希望读者从右向左阅读时,应使用某种方法告诉读者,这时就可以使用方向指示符。可以将方向指示符放在关联名称的某一侧,以向读者说明应如何理解关联名称。上图中两个关联名称都使用方向指示符,该指示符是两个黑三角。事实上,第一个不必使用,因为该名称的阅读顺序符合人们的阅读习惯;只有在阅读顺序不符合人们的阅读习惯时,才有必要使用方向指示符。
对关联进行命名是为了清晰而简洁地说明对象间的关系,同时可以用于指导对象之间的通信方式定义,也决定每个对象在通信中所扮演的角色。
2.关联的端点
为了定义对象在关联中所扮演的角色,UML将关联中的每个端点都作为具有相应规则的独立实体。因而,在“holds”关联中Person对象的参与跟Car对象的参与是不同的。
每个关联端点都包含了如下内容:端点上的对象在关联中扮演什么角色,有多少对象可以参与关联,对象之间是否按一定的顺序进行排列,是否可以用对象的一些特征对该对象进行访问,以及一个端点的对象是否可以访问另一个端点的对象等。
关联端点可以包含诸如角色、多重性、定序、约束、限定符、导航性、可变性等特征中的部分或者全部。
3.关联中的角色
角色是关联关系中一个类对另一个类所表现出来的职责,任何关联关系中都涉及与此关联有关的角色,也就是与此关联相连的类的对象所扮演的角色。在下图中,人在“enjoy”这一关联关系中扮演的是观众这一角色;演出是演员表演的结果,因而Performance对象所扮演的角色就是演员。

与关联名称相比,角色名称从另外一个角度描述了不同类型的对象是如何参与关联的。关联中的角色通常用字符串命名,角色可以是名词或名词短语,以解释对象是如何参与关联的。类图中角色名通常放在与此角色有关的关联关系(代表关联关系的直线)的末端,并且紧挨着使用该角色的类。角色名不是类的组成部分,一个类可以在不同的关联中扮演不同的角色。
由于角色名称和关联名称都被用来描述关系的目的,所以角色名称可以代替关联名称,或者两者同时使用。例如,上图中前面的模型同时使用了关联名称和角色名称,后面的模型只使用了角色名称,这两种表示关联的方法都是可行的。
与关联的名称不同,位于关联端点的角色名可以生成代码。每个对象都需要保存一个参考值,该参考值指向一个或者多个关联的对象。在对象中,参考值是一个属性值,如果只有一个关联,就只有一个属性来保存参考值。在生成的代码中,属性使用参考对象的角色名命名。
4.可见性
相关人员可以使用可见性符号修饰角色名称,以说明该角色名称可以被谁访问,如下图所示。

上图中,Performance类的参考值指向角色名称“-Audience”,该角色名称前面的“-”表示可见性类型为Private,这说明类Performance包含一个私有属性,它保存了一个参考值指向Person对象。
提示
由于上图中属性的可见性为Private,所以要想访问该属性则需要使用一个可见性不是Private的操作。另外,UML 2.0版本中关联端点已不再使用可见性的概念。
5.多重性
关联的多重性指的是有多少对象可以参与关联,它可用来表达一个取值范围、特定值、无限定的范围或者一组离散值。在UML中,多重性是用由数字标识的范围来表示的,其格式为“mininum..maxinum”,其中mininum和maxinum都表示int类型。例如0..9,它所表示的范围的下限为0,上限为9,下限和上限用两个圆点进行分隔,该范围表示所描述实体可能发生的次数是0到9中的某一个值。
多重性也可以使用符号“*”来表示一个没有上限或者说上限为无穷大的范围。例如,范围0..*表示所有的非负整数。下限和上限都相同的范围可以简写为一个数字。例如,范围2..2可以用数字2来代替。
除上面介绍的表示外,多重性还可以用另外一种形式来表示,即用一个由范围和单个数字组成的列表来表示,列表中的元素通常以升序形式排列。例如,有一个实体是可选的,但如果发生的话,就必须至少发生两次以上,那么在建模时就可以用多重性0,3..*来表示。
赋给一个关联端点的多重性表示在该端点可以有多个对象与另一个端点的一个对象关联。例如,下图中所示的关联具有多重性,它表示一个人可以拥有0辆或者多辆小汽车。

6.定序
在关联中使用多重性时可能会有多个对象参与关联。当有多个对象时还可以使用定序约束,定序就是指将一组对象按一定的顺序排列。UML规范中的布尔标记值ordered用于说明是否要对对象进行排序。要指出参与关联的一组对象需要按一定的顺序排列,只需将关键字{ordered}置于关联端点处就可以了。例如,下图中一个Person对象可以拥有多个Car对象,这些Car对象被要求按照一定的顺序进行排列。如果对象不需要按照一定的顺序进行排列,那就可以省略关键字{ordered}。

前面已经介绍过在系统实现时关联被定义为保存了参考值的属性,该参考值指向一组参与关联的对象。在为对象规定了定序约束后,对象必须按照一定的顺序排列,因此实现关联时必须考虑关联的标准,以及如何在保持正确顺序的前提下向队列中添加对象或者从队列中删除对象。
7.约束
UML定义了3种扩展机制:标记值、原型和约束。约束定义了附加于模型元素之上的限制条件,保证了模型元素在系统生命周期中的完整性。约束的格式实际上是一个文本字符串(使用特定的语言表达),几乎可以被附加到模型中的任何元素上。约束使用的语言可以是OCL、某种编程语言,甚至也可以是自然语言,如英文、中文等。
在关联端点上,约束可以被附加到{ordered}特性字符串里。例如,下图中,ordered后面添加了一个约束,该约束限定与Person对象关联的一个Car对象的价格不能超过$100000。

约束规定了实现关联端点时必须遵守的一些规则。如前所述,关联是使用包含参考对象的属性来表现的,在系统实现时需要编写一些方法,以创建或者改变参考值,关联端点的约束就是在这些方法中实现的。
关联端点上的约束还可以用于限定哪些对象可以参与关联。例如,某国为了保护本国的汽车制造业,规定本国公民只能购买国产的小汽车,而不能购买市场上的非国产小汽车,这时可以在模型中使用约束,用布尔值homemade来表示,如下图所示。

提示
约束条件的作用对象是靠近它的关联端点的类,在模型中使用约束时要使约束条件靠近它所作用的类。在不熟悉OCL之前,可以使用自然语言表示约束。
8.限定符
限定符定义了被参考对象的一个属性,并且可以将该属性作为直接访问被参考对象的关键字。当需要使用某些信息作为关键字来识别对象集合中的一个对象时,可以使用限定符。使用限定符的关联被称为受限关联。
限定符提供了一种切实可行的实现直接访问对象的方法。要建立限定符的模型,首先必须确定希望直接访问的对象的类型,以及提供被访问对象的类型,限定符被放在希望实现直接访问的对象附近。
在现实系统中,限定符和用作对象标识的属性之间通常是密切联系的。例如,下图中,Class具有每个学生的信息,每个学生都有唯一的标识。但是,该类图中并没有清楚地指出每个学生的编号是否是唯一的。

为了能够在类图中描述这一约束,建模者通常将用作标识的属性stuID作为类Class的一个限定符,如下图所示。对于识别对象身份这类问题来说,没有必要在数据模型中引入一个充当标识的属性,而应该用限定符来描述对象的标识。

9.导航性
导航性用来描述一个对象通过链进行导航访问另一个对象。也就是说,对一个关联端点设置导航属性意味着本端的对象可以被另一端的对象访问。导航性使用置于关联端点的箭头表示。如果存在箭头,就表示该关联端点是可导航的,反之则不成立。例如,下图中,“holds”关联靠近Car类端点的导航性被设置为真。UML使用一个指向Car的箭头表示,这意味着另一端的Person对象可以访问Car对象。

所以,如果两个关联端点都是可导航的,就应该在关联的两个端点处都放置箭头。但在这种情况下,大多数建模工具采用了默认的0表示方法,即两个箭头都不显示。原因是:大多数关联都是双向的。因而,除非特别声明,一般都把代表导航性的箭头省略了。但是,如果采用默认表示方法,在生成代码时指向关联对象的参考值将被作为对象属性实现,并且会有一些操作负责处理该属性。操作和属性最终被写成代码,其中自然也包括了作为关联端点一部分的导航性,这样势必会增加代码量,并且增加编码和维护方面的开销。
提示
千万不要把导航箭头和方向指示符混淆了,前者一般被置于关联直线的尾部,而方向指示符则置于关联名称的左侧或右侧。
10.可变性
可变性允许建模者对属于某个关联的链进行操作,默认情况是允许任何形式的编辑。例如,添加、删除等。在UML中,可变性的默认值可以不在模型中表现出来。但是,如果需要对可变性做些限定,则需要将可变性的取值放在特性字符串中,和定序以及约束放在一起。在预定义的可变性选项中,{frozen}表示链一旦被建立,就不能移动或者改变。如果应用程序只允许创建新链而不允许删除链,则可以使用{addOnly}选项。
如下图所示为Contract类和Company类之间的关联模型。它表示某大学和某建筑公司签订合同,由建筑公司负责建造该大学的图书馆,合同是两者之间的法定关系,为了避免意想不到的错误删除,在该关联的Contract端点上设置了{frozen}特性。

4.4.2 关联类
有时关联本身会引进新类,当想要显示一个类涉及两个类的复杂情况时,关联类就显得特别重要。关联类就是与一个关联关系相连的类,它并不位于表示关联关系的直线两端,而是对应一个实际的关联,用关联类表示该关联的附加信息。关联中的每个连接与关联类中的一个对象相对应。
虽然类的属性描述了实例所具有的特性,但有时却需要将对象的有关信息和对象之间的链接放在一起,而不是放在不同的类中。如下图所示演示了Student和Course之间的Elect关联。

关联类是一种将数据值和链接关联在一起的手段,使用关联类可以增加模型的灵活性,并能够增强系统的易维护性,因此应该在模型中尽量使用关联类。UML中,关联类是一种模型元素,它同时具有关联和类的特性。
关联类和其他类非常相似,两者之间的区别就在于对它们的使用需求不同。一般的类描述的都是某个实体,即看得见摸得着的东西。而关联类描述的则是关系,它可以像关联那样将两个类连接在一起,也可以像类一样具有属性,其属性用来存储相应关联的信息。
如果用户需要记录学生所选课程的成绩,再使用上图就不能符合其要求了。重新以学生、课程和成绩为例,课程的得分并不是学生本来就有的,只有在学生选修了某门课程后,才会有所选课程的得分。也就是说,课程的得分可以将学生和课程关联起来。如下图所示的关联类用来存储学生选修的某一门课程的成绩,该关联类代替了上图中的关联关系。关联类的名称可以写在关联的旁边,也可以放在类标志的名称分栏当中,关联类的标志要用一条虚线与它所代表的关联连接起来。

假设要求每个学生必须明确登记所选的课程,那么每个登记项中就应包含所选课程的得分及其授课学期。可以认为班级是由若干名选修同一课程的学生组成的,将班级定义为登记项的集合,即班级是由特定学期选修相同课程的学生组成的。如下图所示,通过一个用来识别对应于特定类的登记项的关联可以描述这种情况。

4.4.3 或关联与反身关联
前面已经介绍过一个类可以参与多个关联关系,如下图所示是保险业务的类图。个人可以同保险公司签订保险合同,其他公司也可以同保险公司签订保险合同。但是,个人持有的合同不同于一般公司持有的合同。也就是说,个人与保险合同的关联关系不能跟公司与保险合同的关联关系同时发生。当这两个关联不能同时并存时,应该怎样表示呢?

答案很简单,UML提供了一种或关联来建模这样的关联关系。或关联是指对多个关联附加约束条件,使类中的对象一次只能参与一个关联关系。或关联的表示方法如下图所示,当两个关联不能同时发生时,用一条虚线连接这两个关联,并且虚线的中间带有{OR}关键字。

或关联以及前面介绍的其他关联都涉及了多个类。但是,有时候参与关联的对象属于同一个类,这种关联被称为反身关联。例如,不同的飞机场通过航线关联起来,用Airport类表示机场,那么Airport对象之间的关联关系就只涉及了一个类。
当关联关系存在于两个不同的类之间时,关联直线从其中的一个类连接到另一个类。而如果参与关联的对象属于同一个类,那么关联直线的起点和终点都是该类,如下图所示。

上图中,该关联只涉及一个Airport类。反身关联通常要使用角色名称。在二元关联中描述一个关联时需要使用类名称,但在反身关联中只使用类表达关联的意义可能比较模糊,而使用角色名则会更清晰一些。
4.4.4 聚合关系
聚合(Aggregation)关系是在关联之上进一步的紧密耦合,用来表明一个类实际上不拥有但可能共享另一个类的对象。聚合关系是一种特殊的关联关系,它表示整体与部分的关系,且部分可以离开整体而单独存在。在聚合关系中,一个类是整体,它由一个或者多个部分类组成。当整体类不存在时部分类仍能存在,但是当它们聚集在一起时就用于组成相应的整体类。例如,车和轮胎就可以看作是聚合关系,车为整体,轮胎为部分,轮胎离开车后仍然可以存在。
在表示聚合关系时,需要在关联实线的连接整体类那一端添加一个菱形,如下图所示演示了一个简单的聚合关系。

在上图中,CPU类和Monitor类与Computer类之间的关系远比关联关系更强。CPU类和Monitor类都可以单独存在,但当它们组成Computer类时,就会变为整个计算机的组成部分。
提示
由于聚合关联的部分类可以独立存在,这意味着当整体类销毁时,部分类仍可以存在。如果部分类被销毁,整体类也将能够继续存在。
4.4.5 组合关系
在类的众多关系中,再加强一步的耦合是组合关系,组合关系也是一种特殊的关联关系,在某种情况下,也可以说它是一种特殊的聚合关系。组合关系是比聚合关系还要强的关系,它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期。
组合关系和聚合关系很相似,都是整体与部分的关联关系,但是它们之间的不同之处在于部分不能离开整体而单独存在,当整体类被销毁时,部分类将同时被销毁。例如,公司和部门是整体和部分的关系,没有公司,就不存在部门。
组合关系所表达的内涵是为组成类的内在部分建模。表示组成关系的符号与聚合关系类似,但是端末的菱形是实心的。如下图所示为一个简单的组合关系示例图。

上图中代表数据库的整体类DBEmployee由表TableEmployee和表Employee组成,这些关联使用组合关系表示。如果数据库不存在了,数据库中的表也就不存在了。
组合关系还可以进行嵌套,如下图所示。

上图中添加了Record类,可以将该类作为TableEmployee的部分类。该图也说明表TableEmployee中有0个或者0个以上的记录,也表达了记录不能离开表单独存在这一客观情况。