跳转至

入门(2) 快速读懂结构间的关系

研发们或者技术经理们应当有体验过这种感觉,那就是“感觉我讲得挺好的,但是对方就是没能理解”。其实出现这种问题的原因往往不是因为对方理解能力差,或则自身表达不够,而是因为双方有较大的“知识落差”。那么“知识落差”到底是什么意思呢?其实很简单,就是双方各自的“知识链”不同,因此对相同事物的看法和表述不同,从而导致了理解的误差,形成了当前难以沟通的局面。但是“知识链”是几乎无法完全相同的,毕竟人的经历和学识都各不相同。那该如何解决这样的问题呢?最好的方法就是在沟通的内容范围内,用相互理解的语言进行沟通,即构建受限的“知识链”。这样哪怕彼此的三观不同,也能在工作内容中进行有效的沟通。而UML就是承接了这种责任的建模语言。

在阅读此篇前,应当先阅读《UML系列(1):认识UML踏入设计之路》以对UML有一个基础的认识。同时应当具备一定的面向对象编程(OOP)的思想,这样才能真正的发挥UML的用处。

人际关系图
人际关系图

我们知道,在现实世界中人、事物间的关系是非常重要的,有了关系才有了因果,它们在一个相互的作用下共同组成了我们这个纷杂的世界。而在程序设计中,各系统、组件、对象间的关系也是极其重要的,因此学会正确的表达其关系就成为了沟通或设计的首要任务。

在UML中,有如下几种常见关系:

  • 关联(association)
  • 继承(inheritance)/泛化(generalization)
  • 依赖(dependency)
  • 实现(realization)
  • 聚合(aggregation)
  • 组成(composite)

在陈述上述关系之前,我们需要知道“关系之间是存在 约束 的,同时也能用 构造型 来创造新的关系。”


关联

当类之间在概念上有连接关系时,类之间的连接叫做 关联 (由于关联表达的是连接,因此还能用关联图去表达空间的位置关系)。比如在篮球游戏中会有如下关系:

关联示例图-1
关联示例图-1

这样的一幅图表达的内容是:“队员(Player)在球队(Team)打球。且队员是雇员(Employee),球队是雇主(Employer)。一个球队可以有5~10个队员”。当然,“队员”和“球队”代表的程序中的两个类。你看懂了吗?没看懂的话没关系,接着对上图进行分解解释:

  • 先陈述第一句话“队员在球队打球”,这句话表达出了队员和球队之间的联系。那么在UML中可以如此表达,开始可以先说明“队员”和“球队”之间存在关联,这里用一条直线表示。从句子中不然发现“队员“是关系的发起者,因此在关联线的中间用箭头表示这样的关系,同时在方向后方添加描述“Plays on”来对该方向做进一步解释,最终就表达出了我们要的意思了,如下图所示:

    队员在球队打球示例图
    队员在球队打球示例图

  • 接着表达的是第二句“队员是雇员,球队是雇主”,这句话表达出了队员和球队各自的角色。在UML中可以如下表达,在接近类的关联线的地方上表明每个类的角色,于是形成了下图:

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

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

球队雇佣队员示例图
球队雇佣队员示例图

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

一个球队有前锋,中锋和后卫示例图
一个球队有前锋,中锋和后卫示例图

约束

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

关联约束示例图
关联约束示例图
  • UML用 花括号 来表示约束,在受约束的类附近加一个花括号扩起来的“ordered”来表示约束顾客的次序。

约束关系还有一种是“Or(或)”,比如:“大学生(HighSchoolStudent)选修课可以选择诗歌(Poetry)或商务(Commercial)”,UML可以如下表示:

约束or示例图
约束or示例图
  • 在两个关联线之间连一条虚线,并在虚线之上标识“ {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建模也有助于技术经理、架构师、研发工程师等技术相关人员间的沟通,降低“知识落差”带来的沟通困难,能有效提高开发出一个符合大家预期的系统的概率。

评论