入门(2) 快速读懂结构间的关系
研发们或者技术经理们应当有体验过这种感觉,那就是“感觉我讲得挺好的,但是对方就是没能理解”。其实出现这种问题的原因往往不是因为对方理解能力差,或则自身表达不够,而是因为双方有较大的“知识落差”。那么“知识落差”到底是什么意思呢?其实很简单,就是双方各自的“知识链”不同,因此对相同事物的看法和表述不同,从而导致了理解的误差,形成了当前难以沟通的局面。但是“知识链”是几乎无法完全相同的,毕竟人的经历和学识都各不相同。那该如何解决这样的问题呢?最好的方法就是在沟通的内容范围内,用相互理解的语言进行沟通,即构建受限的“知识链”。这样哪怕彼此的三观不同,也能在工作内容中进行有效的沟通。而UML就是承接了这种责任的建模语言。
在阅读此篇前,应当先阅读《UML系列(1):认识UML踏入设计之路》以对UML有一个基础的认识。同时应当具备一定的面向对象编程(OOP)的思想,这样才能真正的发挥UML的用处。

我们知道,在现实世界中人、事物间的关系是非常重要的,有了关系才有了因果,它们在一个相互的作用下共同组成了我们这个纷杂的世界。而在程序设计中,各系统、组件、对象间的关系也是极其重要的,因此学会正确的表达其关系就成为了沟通或设计的首要任务。
在UML中,有如下几种常见关系:
- 关联(association)
- 继承(inheritance)/泛化(generalization)
- 依赖(dependency)
- 实现(realization)
- 聚合(aggregation)
- 组成(composite)
在陈述上述关系之前,我们需要知道“关系之间是存在 约束 的,同时也能用 构造型 来创造新的关系。”
关联¶
当类之间在概念上有连接关系时,类之间的连接叫做 关联 (由于关联表达的是连接,因此还能用关联图去表达空间的位置关系)。比如在篮球游戏中会有如下关系:

这样的一幅图表达的内容是:“队员(Player)在球队(Team)打球。且队员是雇员(Employee),球队是雇主(Employer)。一个球队可以有5~10个队员”。当然,“队员”和“球队”代表的程序中的两个类。你看懂了吗?没看懂的话没关系,接着对上图进行分解解释:
-
先陈述第一句话“队员在球队打球”,这句话表达出了队员和球队之间的联系。那么在UML中可以如此表达,开始可以先说明“队员”和“球队”之间存在关联,这里用一条直线表示。从句子中不然发现“队员“是关系的发起者,因此在关联线的中间用箭头表示这样的关系,同时在方向后方添加描述“Plays on”来对该方向做进一步解释,最终就表达出了我们要的意思了,如下图所示:
-
接着表达的是第二句“队员是雇员,球队是雇主”,这句话表达出了队员和球队各自的角色。在UML中可以如下表达,在接近类的关联线的地方上表明每个类的角色,于是形成了下图:

- 最后表达最后一句话“一个球队可以有5~10个队员”,这句话表达出了队员和球队的数量关系。在UML中称为多重性(multiplicity),表示方法是在参与关联的类附近的关联线上注明多重性的数值,于是形成了下图:

有时候关联关系从不同的角度来看是不同的,比如上面描述了“队员”是雇员、球队是“雇主”,因此还可以表达成“球队雇佣队员”,这句话的发起者是“球队”,因此我们在关联线中间添加箭头,同时用“Employs”进行描述,其UML表示如下图所示:

有时关系可能不是1对1的,可能是n对1的,比如:“一个球队有前锋,中锋和后卫”。这时可以用UML这么表达多个类关联1个类:

约束¶
开始有提到过“关系之间是存在约束的”,因此关联也有这样的表达。比如:“银行柜台服务员(Bank Teller)为顾客(Customer)服务(serve),但是服务的顺序要按照顾客排队的次序进行”,此时UML可以如下表示:

- UML用 花括号 来表示约束,在受约束的类附近加一个花括号扩起来的“ordered”来表示约束顾客的次序。
约束关系还有一种是“Or(或)”,比如:“大学生(HighSchoolStudent)选修课可以选择诗歌(Poetry)或商务(Commercial)”,UML可以如下表示:

- 在两个关联线之间连一条虚线,并在虚线之上标识“ {or} ”来表示这种约束。
关联类¶
和类一样,关联也可以有自己的属性和操作,这样的关联称为 关联类(association class) 。比如:“队员”和“球队”之间的关系是通过“合约(Contract)”关联起来的,而合约的签订由“总经理(GeneralManager)”商定(Negotiates),因此我们可以用UML表达这样的关联,如下所示:

- 关联类也是一个类,因此“合约”的表达方式跟“队员”或“球队”是一样的,只是需要 通过一条虚线把关联类和对应的关联线连接起来 ,以表示该类是关联线的类,即关联类。
链¶
前面我们说过,关联类也是一个类,因此正如对象是类的实例一样,关联也有自己的实例,关联类的实例我们称之为 链。
在“UML入门系列(1):认识UML踏入设计之路”中有提到过实例(对象)的表达方式:
- 类名前提供对象名称,并用冒号分隔。且对象名称的首个单词的首字母是小写的
- 对象名和类名有下划线
链的表达方式也是如此,需要 添加下划线 ,同时链是 专门用来关联实例 的,而不是类。比如:“科比(kobeBeanBryant)”效力于“湖人队(losAngelesLakers)”:

- 这里由于工具的原因,没能将“Playes on”添加下划线,因此在空格的地方追加下划线以做表示。
多重性¶
前文在讲述“队员”与“球队”关系的时候提到了多重性,这里讲一下其表达方式:
- 用
*
表示许多,即>=1
的意思。 - 用
..
表示连续或,即1..10
表达的是1到10中的任一数。 - 用
,
表示序列或,即1,5,7
表达的是1或5或7。
限定关联¶
当关联的多重性是1对多时,就产生了一个问题:查找问题。比如我们想在一个“房间预订列表”中查找其中的一条“预定信息”时,需要有个具体的查找条件,该条件应是“预定信息”的某个属性。我们将表达这种查找(限定)关系称为 限定符(qualifier) :

如上所示,“房间预订列表”与“预订信息”是1对n的关系,通常一个“预订信息”会对应唯一的一个“订单号(orderNumber)”用于“房间预订列表”查询条件,其UML表示如下:

自身关联¶
有时候一个类可能与它自身发生关联,这样的关联被称为 自身关联(reflexive association) 。当一个类的示例可以充当多种角色时,自身关联就可能发生。
比如,一位“车上的人(CarOccupant)”既可能是一位“司机(driver)”,也可能是一位“乘客(passenger)”,那么一个司机可以搭载0到4位乘客,因此其UML表达为:

继承和泛化¶
继承和泛化是OOP的用语,这里不对这两术语进行解释,感兴趣的可以自行搜索。继承是“is a”表达,在各种OOP语言都会提到此概念,比如:“哺乳动物(Mammal)”是一种“动物(Animal)”,“马(Horse)”是一种“哺乳动物”:

- 实线连接父类和子类,且用空心箭头指向父类。
了解对象的知道,除了父类和子类外还有基类(base class)或根类(root class)、叶类(leaf class)、抽象类(abstract class) 的称呼。这些称呼中抽象类是比较特殊的,它表示一个类是不提供实例对象的,在UML也有特定表达方式,就是“将类名用 斜体 书写”:

如上图所示,我们重新回到队员的关系中,上图表明:
- “队员”由“后卫”、“前锋”、“中锋”继承
- “队员”(斜体字)是个抽象类,不提供实例对象。
注意
在UML中子类只需要些那些自己特有的属性和方法,因为继承就表明拥有父类的属性,同时父类的(公有、保护)方法会被子类继承并应用。
依赖¶
当在一个类中使用了另一个类时,我们称之为依赖。依赖用 虚线 连接,用 尖箭头 指向依赖的目标。比如,一个“系统(System)”的“显示表单(DisplayForm)”功能依赖于“表单类(Form)”:

聚合¶
一个类有时是由几个部分类组成的,这种关系是一种“ 部分-整体 ”的关联,我们称之为“聚合”。聚合与“组成”有些类似,区别主要是聚合并 不限定“部分类”只能归属于自己 ,说白了“聚合”是 没有占有欲 的,它仅仅表达它组成了我,但没有要求它只属于我。而“组成”要求“部分类”只能属于自己:

- 整体和部分之间用 实线 连接,且用 空心菱形箭头 指向整体。
聚集跟关联一样,也能表达“Or”的约束,表达方式一样,“在两个部分的连线中间连接一条虚线,并写上{or}”,其表示整体包含两者间的其中一个。
组成¶
组成时强类型的聚合,组成有极强占有欲,要求部分必须只能属于自己,即只能属于一个整体。组成是表达类内部结构的一种方式,用于“组成结构图”(UML1.x也称为语境图)中。其UML用 实线 连接部分和整体,并用 实心菱形箭头 指向整体:

实现¶
类由属性和方法(接口)组成,实现表达的就是类与接口间的关系。UML用虚线连接类和接口,并用 空心箭头 指向接口,比如,“洗衣机(WashineMachine)”实现了“控制旋钮(ControlKnob)”:

还有一种表示法(省略表示法),将接口表示为小圆圈,又称为棒糖图(lollipop diagram),这也是我比较喜欢的一种表达方式:

那么接口的出现必然要被使用,比如一个“人(Person)”使用“控制旋钮”,用UML表达如下:

- 可以发现使用接口的表达方式与依赖一样,可以理解为:“人”需要通过“控制旋钮”才能使用“洗衣机”,因此“人”依赖于“控制旋钮”。
还有一种跟“棒糖图”结合的表达使用接口的方式,这也是我最喜欢的方式:

结语¶
学习UML建模对于程序员来说有莫大帮助,可以帮助自己梳理程序,将思绪可视化表达出来,有助于其开发复杂的大型程序。
同时学习UML建模也有助于技术经理、架构师、研发工程师等技术相关人员间的沟通,降低“知识落差”带来的沟通困难,能有效提高开发出一个符合大家预期的系统的概率。