
4.2 泛化关系
类与类之间的关系有多种,如依赖、实现和泛化等。泛化描述了一般事物与该事物的特殊种类之间的关系。在解决复杂问题时,通常需要将具有共同特性的元素抽象成类,并通过增加其内容而进一步分类。例如,车可以分为火车、汽车、摩托车等。它们也可以表示为泛化关系,下面将详细介绍与泛化相关的知识。
4.2.1 泛化的含义和用途
应用程序中通常会包含大量紧密相关的类,如果一个类A的所有属性和操作能被另一个类B所继承,则类B不仅可以包含自己独有的属性和操作,而且可以包含类A中的属性和操作,这种机制就是泛化(Generalization)。
UML中,继承是泛化的关键。父类与子类各自代表不同的内容,父类描述具有一般性的类型,而子类则描述该类型中的特殊类型。从另外一种方法来说,泛化是一种继承关系,表示一般与特殊的关系,它指定了子类如何特化父类的所有特征和行为。例如,老虎是动物的一种,既有老虎的特性,也有动物的共性。
泛化关系是一种存在于一般元素和特殊元素之间的分类关系。这里的特殊元素不仅包含一般元素的特征,而且包含其独有的特征。凡是可以使用一般元素的场合都可以用特殊元素的一个实例代替,反之则不行。
泛化关系只使用在类型上,而不用于具体的实例。泛化关系描述了“is a kind of”(是……的一种)的关系。例如,金丝猴、猕猴都是猴子的一种,东北虎是老虎的一种。在采用面向对象思想和方法的地方,一般元素被称为超类或者父类,而特殊元素被称作子类。
UML规定,泛化关系用一个末端带有空心三角形箭头的直线表示,有箭头的一端指向父类。如下图演示了一个简单的泛化关系,其中Monkey类表示父类或超类,该类包含Golden Monkey和Macaque两个子类,这两个子类不仅继承了父类中的所有属性和操作,同时也可以拥有自己特定的属性和操作。

泛化主要有两个用途:第一个用途是,当变量被声明承载某个给定类的值时,可使用类的实例作为值,这被称作可替代性原则。该原则表明无论何时祖先被声明了,其后代的一个实例都可以被使用。例如,如果猴子父类Monkey被声明,那么一个金丝猴或者猕猴的对象就是一个合法的值。第二个用途是通过泛化使多态操作成为可能,即操作的实现是由它们所使用的对象的类决定的,而不是由调用者决定的。
4.2.2 泛化的层次与多重继承
泛化可能跨越多个层次。一个子类的超类也可以是另一个超类的子类。如下图所示为具体层次结构的泛化。

在上图中,AutoMobile类是Car类的子类,不仅如此,AutoMobile类还是PassengerCar类和TouringCar类的超类,这就显示出了泛化的层次结构。子类和超类这两个术语是相对的,它们描述的是一个类在特定泛化关系中所扮演的角色,而不是类自身的内在特性。在该图中,Bicycle类表示Car类中的另外一个子类,也可以使用3个点表示省略号,如果为3个点时,表明Car类除了图中所显示的子类AutoMobile外,还可以拥有其他子类。
对泛化层次图中的一个类而言,从它开始向上遍历到根时经历的所有类都是其祖先,从它开始向下遍历时遇到的所有类都是其后代。这里的“上”和“下”分别表示“更一般的类”和“更特殊的类”。
面向对象设计的最佳原则之一是避免紧密耦合的类,使一个类改变时不必改变一系列相关的其他类。由于泛化使用子类可以看见父类内部的大部分内容,使得子类紧密耦合于父类,所以泛化是类关系中最强的耦合形式。因此,使用泛化的基本原则是:只有在一个类确定是另外一个类的特殊类型时才使用泛化。
多重继承在UML中的正式术语称为多重泛化。多重泛化使同一个子类不仅可以像上图中的Car类那样具有多个子类,而且可以拥有多个父类,即一个类可以从多个父类派生而来。例如,坦克是一种武器,但它同时也可作为一种车来使用。多重泛化在UML中的表示方法如下图所示。

在上图中,一个子类带有两个指向超类的箭头。通过Vehicle类的drive、reverse、park、start和stop操作确定了属于Vehicle类的行驶功能,通过Weapon类的load、aim和fire操作确定了属于Weapon类的破坏功能。ram和radio操作则是Tank类独有的。
虽然UML支持多重泛化,但是通常情况下,实际应用中的泛化使用并不多。其主要原因在于当两个父类具有重叠的属性和操作时,多重继承里的父类会存在错综复杂的问题。因此,多重继承在面向对象的系统开发中已经被禁止,当今流行的一些开发语言(如Java和C#)都不支持多重继承。
4.2.3 泛化约束
泛化约束用于表明泛化有一个与其相关的约束,带有约束条件的泛化也被称为受限泛化。泛化建模约束有两种情况:如果有多个泛化使用相同的约束,可以绘制虚线穿过两个泛化,并且在花括号中标注约束名;如果只有一个泛化,或者多个泛化共享关联的空箭头部分,就只需在朝向空箭头的花括号中注明约束即可,如下图所示。

泛化约束包含4种:不完全约束(Incomplete Constraint)、完全约束(Complete Constraint)、解体约束(Disjoint Constraint)和重叠约束(Overlapping Constraint)。
1.不完全约束
表示类图中没有完全显示出泛化的类,这种约束可以让读者知道类图中显示的内容仅仅是实际内容的一部分,其余内容可能位于其他类图中,如下图所示。

2.完全约束
与不完全约束相对应的是完全约束,当类图中存在完全约束时,表示类图中显示了全部内容,如下图所示。

3.解体约束
表示紧靠约束下面的泛化类不能有子转为通用的类,它比前两种约束更加复杂,如下图所示。

从上图中可以看出,根超类OS有两个子类Windows和Linux。解体约束表示Windows和Linux类都不能共享其他的子类。在该图中,Windows类和Linux类都有各自的子类,但不能从Windows NT类到Linux类绘制一个泛化关联,由于解体约束的存在,Windows NT类不能同时继承Windows和Linux类。
4.重叠约束
与解体约束作用相反的泛化约束,即重叠约束。该类型约束表示两个子类可以共享相同的子类。如下图所示,Database类有两个子类Relational和OLAP,它们共享相同的类DataWarehouse。
