OOP 面对对象: 一次搞懂 UML 类图到底怎么画

OOP 面对对象: 一次搞懂 UML 类图到底怎么画

前言

今天来介绍在 OOP 面对对象编程中常用到的,用于描述对象之间的关系的 类图(Class Diagram)

正文

1. 类图包含元素

首先我们先看看在类图中存在哪些核心要素

1.1 实体:类、抽象类、接口

类图中的实体不外乎就是 类(Class),同时针对其抽象程度,又能再划分出 抽象类(Abstract Class)接口(Interface)

1.2 类属性/方法访问描述符

而在类中的属性(成员变量)、方法,又存在所谓的 访问描述符(Access Descriptor),也就是指该变量or方法对于其他类的可见性,常用的分为以下三种

  • - 表示 private 私有,其他类不可访问
  • # 表示 protected 保护,只有子类可访问
  • + 表示 public 公有,所有类都可访问
  • ~(很少用) 表示 package 包,表示属于同一个包的类可用

1.3 类与类之间的关系

有了实体和实体的属性(包括成员变量和方法)之后,最后一个重要的元素就是描述 类与类之间的关系,在类图中我们通常使用不同样式的线段来表示

下面一个小结我们将一一介绍不同关系的含义

2. 类与类之间的关系

如上图我们看到,类与类之间的关系分成 6 种:

  • 关联 Association
  • 聚合 Aggregation
  • 组合 Composition
  • 依赖 Dependency
  • 泛化 Generalization
  • 实现 Realization

下面我们一个个来看各自的含义

2.1 关联 Association

第一种是 关联 Association,简单来说所谓的关联就是:

一个类持有对另一个类的引用称为 关联

依据不同的持有场景,又可以分为

  1. 单向关联

一个用户(Account)拥有一个地址(Address),也就是用户类单方面"持有"地址类的引用,这时候我们就从 Account 画一条实心箭头指向 Address

  1. 双向关联

有时候也会产生双向关联的场景如上图,这时候我们可以选择两端都加上箭头或是两端都不加上箭头

上面的例子就是一个顾客(Customer)持有购买的产品列表(products),而每个产品对象(Product)持有一个购买者(buyer)的引用

  1. 自关联

当然,引用与自己同类型的不同对象也是非常容易想象的。如上例就是指一个节点(Node)可能持有其子节点数组(children)的引用

2.2 聚合 Aggregation

第二种关系是 聚合 Aggregation。相较于关联(Association)的持有引用的概念,聚合(Aggregation)则存在更严格的限制:

引用与被引用的对象必须存在 包含 关系

也就是说引用者应该属于一个范围比较大的类型,而被引用者应该属于引用者的一部分或是组成

如例子中我们看到一个学校可能拥有很多个学生,我们也可以说一间学校是由很多个学生组成。如此一来学校与学生就产生"包含"关系

2.3 组合 Composition

第三种的 组合 Composition 又与第二种的聚合(Aggregation),非常相似。不同的地方在于:

  • 对于 聚合 来说:被包含的类是可以独立存在的
  • 对于 组合 来说:被包含的类是 不可以 独立存在的

也就是说在组合当中,被包含的对象一离开宿主类型就失去意义

如上图,一颗头一定属于一个人,这个头只有作为人的头才有意义。不同于前一个例子中,一个学生离开了学校,还是可以作为一名学生存在。

2.4 依赖 Dependency

前面提到的关系主要都是持有引用(关联)、包含关系(聚合、组合),接下来的第四个关系 依赖 Dependency 属于一个比较弱的关系。

当我们在一个对象的类当中使用到了另一个对象的引用(参数、成员变量、静态方法调用),这时候我们就说该类 依赖 与另一个对象,因为 该对象的实现是依赖于被使用的类的实现的

如上图我们看到,驾驶(Driver)与车(Car)并没有直接持有或是任何包含关系,但是一个驾驶要开车的时候就一定会用到一台车,这时候我们就可以说一个驾驶员的行为 依赖 与车,或是说依赖于车的实现

2.5 泛化(继承) Generalization

后面两个关系反而是大家比较熟悉的 OOP 编程非常常见的继承。

然而在 UML 中我们通常采用 泛化 Generalization 这种说法,因为不同语言对于类型系统的实现不尽然相同,有的可以多继承(C++)有的不行(Java),有的采用了原型链(JavaScript)来实现类型系统。

所以我们可以统一采用 泛化(Generalization) 的说法,所谓的"子类"仅仅只是对于父类的 扩展,也就是对父类的能力进行泛化,使其具备更多特征和能力

我们可以看到人类(Human)作为基类,持有并描述作为一个人所具有的属性(姓名、年龄)和能力(走路、说话);而我们再定义两个子类用来更具体的描述特定族群的人类:

  • 学生拥有自己专属的学号(studentId)以及行为(学习,当然任何年龄都能学,可能改成考试会更好hh)
  • 老师也拥有自己专属的教职工号(teacherId)以及行为(教学)

2.6 实现 Realization

最后一种则是对于接口的 实现 Realization。前面提到的泛化是一种对于现有类型的扩展、泛化,而有的时候我们会定义没有实现方法的 抽象接口(interface),而不同的对象实现该接口并提供接口方法的具体行为后,我们就可以把这几个类视为具有相同行为的类型(接口类型),这就是所谓的实现

我们可以看到奔跑接口(Runnable)仅仅是描述一种能够奔跑的类型,而没有具体的实现;人类(Human)和狗(Dog)都分别提供对于奔跑接口的实现(实现 run 方法),这时候我们就可以说人类与狗都是 可奔跑的对象(实现 Runnable 类型的类)

2.7 类与类之间的关系小结

看完六种对象之间的关系,我们最后做一个总整理和比较

关系类型描述比较
关联 Association一个类 持有对另一个类的引用两者之间并无明显包含关系、仅仅就是持有引用
聚合 Aggregation一个类 包含 另一个类后者必须作为前者的部件或是组成元素
组合 Composition一个类 包含 另一个类后者除了作为前者的部件或是组成元素之外,还必须是专属于前者的部件(离开前者则无含义的部件)
依赖 Dependency一个类 使用(依赖) 另一个类前者在方法中直接、简介使用到了后者类型(参数、局部变量、静态方法调用)
泛化 Generalization一个类 继承/扩展/泛化 另一个类前者基于一个具体存在的类型(后者)进行扩展
实现 Realization一个类 实现 一个接口的方法前者实现接口的方法后具备接口的特征,也就可以作为接口的类型(如 Runnable)被引用

结语

本篇介绍 UML 中关于 类图(Class Diagram) 的重要图例进行解说,供大家参考。

其他资源

参考连接

TitleLink
五分钟读懂UML类图https://www.cnblogs.com/shindo/p/5579191.html
UML类图中的符号解释https://blog.csdn.net/cabinriver/article/details/8894327
uml 类图中+ - 含义https://blog.csdn.net/u014624241/article/details/74925442
JAVA UML图,类图,接口图,抽象类图https://my.oschina.net/u/4415254/blog/4556668
UML(一):类、接口、抽象类https://www.cnblogs.com/iamkk/p/5861025.html

完整代码示例

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值