研发们或者技术经理们应当有体验过这种感觉,那就是“感觉我讲得挺好的,但是对方就是没能理解”。其实出现这种问题的原因往往不是因为对方理解能力差,或则自身表达不够,而是因为双方有较大的“知识落差”。那么“知识落差”到底是什么意思呢?其实很简单,就是双方各自的“知识链”不同,因此对相同事物的看法和表述不同,从而导致了理解的误差,形成了当前难以沟通的局面。但是“知识链”是几乎无法完全相同的,毕竟人的经历和学识都各不相同。那该如何解决这样的问题呢?最好的方法就是在沟通的内容范围内,用相互理解的语言进行沟通,即构建受限的“知识链”。这样哪怕彼此的三观不同,也能在工作内容中进行有效的沟通。而UML就是承接了这种责任的建模语言。
在阅读此篇前,应当先阅读《UML系列(1):认识UML踏入设计之路》以对UML有一个基础的认识。同时应当具备一定的面向对象编程(OOP)的思想,这样才能真正的发挥UML的用处。
我们知道,在现实世界中人、事物间的关系是非常重要的,有了关系才有了因果,它们在一个相互的作用下共同组成了我们这个纷杂的世界。而在程序设计中,各系统、组件、对象间的关系也是极其重要的,因此学会正确的表达其关系就成为了沟通或设计的首要任务
在UML中,有如下几种常见关系:
在陈述上述关系之前,我们需要知道“关系之间是存在约束的,同时也能用构造型来创造新的关系。”
当类之间在概念上有连接关系时,类之间的连接叫做关联(由于关联表达的是连接,因此还能用关联图去表达空间的位置关系)。比如在篮球游戏中会有如下关系:
关联示例图1
这样的一幅图表达的内容是:“队员(Player)在球队(Team)打球。且队员是雇员(Employee),球队是雇主(Employer)。一个球队可以有5~10个队员”。当然,“队员”和“球队”代表的程序中的两个类。你看懂了吗?没看懂的话没关系,接着对上图进行分解解释:
队员在球队打球示例图
队员是雇员,球队是雇主示例图
一个球队可以有5~10个队员示例图
有时候关联关系从不同的角度来看是不同的,比如上面描述了“队员”是雇员、球队是“雇主”,因此还可以表达成“球队雇佣队员”,这句话的发起者是“球队”,因此我们在关联线中间添加箭头,同时用“Employs”进行描述,其UML表示如下图所示:
球队雇佣队员示例图
有时关系可能不是1对1的,可能是n对1的,比如:“一个球队有前锋,中锋和后卫”。这时可以用UML这么表达多个类关联1个类:
一个球队有前锋,中锋和后卫示例图
开始有提到过“关系之间是存在约束的”,因此关联也有这样的表达。比如:“银行柜台服务员(Bank Teller)为顾客(Customer)服务(serve),但是服务的顺序要按照顾客排队的次序进行”,此时UML可以如下表示:
关联约束示例图
约束关系还有一种是“Or(或)”,比如:“大学生(HighSchoolStudent)选修课可以选择诗歌(Poetry)或商务(Commercial)”,UML可以如下表示:
约束Or示例图
和类一样,关联也可以有自己的属性和操作,这样的关联称为关联类(association class)。比如:“队员”和“球队”之间的关系是通过“合约(Contract)”关联起来的,而合约的签订由“总经理(GeneralManager)”商定(Negotiates),因此我们可以用UML表达这样的关联,如下所示:
关联类示例图
前面我们说过,关联类也是一个类,因此正如对象是类的实例一样,关联也有自己的实例,关联类的实例我们称之为链。
在“UML系列(1):认识UML踏入设计之路”中有提到过实例(对象)的表达方式:
链的表达方式也是如此,需要添加下划线,同时链是专门用来关联实例的,而不是类。比如:“科比(kobeBeanBryant)”效力于“湖人队(losAngelesLakers)”:
链示例图
前文在讲述“队员”与“球队”关系的时候提到了多重性,这里讲一下其表达方式:
*
表示许多,即>=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建模也有助于技术经理、架构师、研发工程师等技术相关人员间的沟通,降低“知识落差”带来的沟通困难,能有效提高开发出一个符合大家预期的系统的概率。