下面先简单列举一下subsets和redefines的特点:
关联端点具有标识subsets表明subsets一端的对象集合是被subsets一端的对象集合的子集。此时,subsets一端的类与被subsets一端的类之间必须存在继承关系。同时,subsets一端的角色名一定与被subsets一端的角色名不同。
关联端点具有标识redefines表明这是对被redefines的关联端点的重定义。此时,有两种情况:如果关联一端由类拥有,则redefines一端的类与被redefines一端的类之间一定有继承关系;如果关联一端由关联拥有,则redefines一端的关联与被redefines一端的关联之间一定有继承关系。同时,redefines一端的角色名一定与被redefines一端的角色名相同(当然实际上不相同也可以,但是一律设置为相同也不会有错误,并且这样还会使类图更加清晰)。
标识subsets和redefines的作用范围是关联端点,而不是关联。因此会出现关联的两个端点之中出现不同标识的情况。
一个特殊情况:
如果具有继承关系的两个关联联接的是两个相同的类,则如果希望一组同一侧的关联端点之间有重定义关系,则这组关联端点必须是关联拥有,而不能是(关联所联接的)类拥有。原因也很简单,因为一个端点类内部不存在继承关系,在这种情况下,有继承关系的只能是都以此类作为一个端点的两个关联。图一中的下面两个关联之间就是这种情况;
在继承路径上,subsets标识和redefines标识可以交替出现:
标识subsets所修饰的角色名是其所在关联端点之前的那一个关联端点的角色名。
证明这一点的一个例子是Figure7.5,Namespace和NamedElement之间有一个组合关联(记为Asso1)(其继承自Element的递归组合关联,记为Asso0),而Namespace和Constraint之间的组合关联(记为Asso2)则继承自Asso1,但是,Asso2两端的标记分别是{subsets namespace}和{subsets ownedMember},这是Asso1两端的角色名,而不是{subsets owner}和{subsets ownedElement},这是Asso0两端的角色名。
标识redefines所修饰的角色名也是其所在关联端点之前的那一个关联端点的角色名。
举个例子,假定有如下继承序列:
B继承自A,C继承自B,D继承自C,E继承自D,F继承自E,G继承自F。它们都是一组二元关联同侧的角色名。
此时可以有如下subsets和redefines的序列:
B subsets A、C subsets B、D redefines C、E subsets D、F subsets E、G subsets F;
如果一个关联Asso3的所有直接特化关联在Asso3的同侧的标记都是subsets,则Asso3此侧的标记可以包含{readOnly, union}。证明这一点的一个例子在Figure7.13和Figure8.4:
前一个图中,Constraint和ValueSpecification之间的二元关联(记为Asso4)直接继承自上文中的Asso0,并且其两端的标记都是subsets,但是,后一个图中,IntervalConstraint和Interval之间的二元关联(记为Asso5)继承自Asso4,而Asso5两侧的标记都是redefines,这意味着两个事实:
Asso0的标记{readOnly, union}并未受到影响;
Asso4不可能再有标记{readOnly, union}。
出现在关联端点处的redefines标记,有四种可能:
P1、一对类之间的两个关联(关联拥有端点)的情况:端点(E1)由关联(A1)拥有(即A1的E1端既无实心黑点,也无导航箭头),另一个关联A2在E1一侧的端点也由A2拥有。同时,A1和A2在E1一侧可以具有相同的角色名,也可以不具有相同的角色名,这一点并不重要,重要的是redefines所重定义的角色名要与被重定义的角色名一致。
在图一的左右两个类之间,在其中下面三个关联中,它们左侧的角色名都是templateParameter,并且最下面的关联在左侧有标记redefines templateParameter,那么这个redefines重定义的是其上两个关联中的哪个关联的左端点?观察最下面的一个关联,其右侧的标记中包含“subsets default”,而其上的两个关联中,只有紧邻其上的那个关联的右侧的角色名是“default”,因此最下面的关联继承自紧邻其上的那个关联;
P2、一对类之间的两个关联(类拥有端点)的情况:关联(A1)的此端点(E1)由类(C1)拥有(即A1的E1端既有实心黑点,也有导航箭头,或者仅有实心黑点),另一个关联A2在E1一侧的端点也由C1拥有。同时,A1和A2在E1一侧可以具有相同的角色名,也可以不具有相同的角色名,这一点并不重要,重要的是redefines所重定义的角色名要与被重定义的角色名一致。
图一、摘自Figure7.3 Template,P1、P2的例子。
P3、两对类之间的两个关联(关联拥有端点)的情况:端点(E1)由关联(A1)拥有(即A1的E1端既无实心黑点,也无导航箭头),另一个关联A2在E1一侧的端点也由A2拥有。重要的是:A1所联接的两个类必须要是A2所联接的两个类的子类。同时,A1和A2在E1一侧可以具有相同的角色名,也可以不具有相同的角色名,这一点并不重要,重要的是redefines所重定义的角色名要与被重定义的角色名一致。
在图二中:
Duration继承自ValueSpecification(见Figure8.3);
DurationInterval继承自Interval;
因此Duration和DurationInterval之间的两个关联(A1和A2)就重定义了ValueSpecification和Interval之间的两个关联(A3和A4)。其中A1和A2在durationInterval一侧的情况就与A3和A4在interval一侧的情况完全相同,即都是关联拥有关联端点。
并且,这还意味着A1继承自A3,A2继承自A4。
P4、两对类之间的两个关联(类拥有端点)的情况:关联(A1)的此端点(E1)由类(C1)拥有(即A1的E1端既有实心黑点,也有导航箭头,或者仅有实心黑点),类C2为另一个关联A2的端点类,并且A2在E1一侧的端点也由C2拥有。重要的是:A1所联接的两个类必须要是A2所联接的两个类的子类。同时,A1和A2在E1一侧可以具有相同的角色名,也可以不具有相同的角色名,这一点并不重要,重要的是redefines所重定义的角色名要与被重定义的角色名一致。
在图二中:
Duration继承自ValueSpecification(见Figure8.3);
DurationInterval继承自Interval;
因此Duration和DurationInterval之间的两个关联(A1和A2)就重定义了ValueSpecification和Interval之间的两个关联(A3和A4)。其中A1和A2在Duration一侧的情况就与A3和A4在ValueSpecification一侧的情况完全相同,即都是类拥有关联端点。
并且,这还意味着A1继承自A3,A2继承自A4。
图二、摘自Figure8.4,P3、P4的例子。
标记subsets的含义比较简单,就不举例说明了。
最后再给大家出一个曾经困扰了本人很久的问题:
在UML2.5.1中,关联的一个端点如果由关联拥有,则只有当关联此端有{redefines}标记时,才存在关联的泛化关系。但是直觉看,关联此端有{subsets}时,说明subsets的一端的对象集合是被subsets一端的对象集合的子集,这是一种继承关系,因此关联此端有{subsets}标记才应该被看作是有关联泛化的存在。为什么UML2.5.1没有采用符合直觉的方案?
对于这个问题欢迎大家讨论留言。
下一篇文章将通过一个看似是Bug而实际上不是Bug的例子和一个看似不是Bug而实际上是Bug的例子来从不同的角度说明如何理解“UML2.5.1”。
参考文献:
UML2.5.1