UML 中类图与类的关系
类图(Class Diagram)描述系统中类的静态结构。不仅定义系统中的类,表示类之间的联系如关联、依赖、聚合等,也包括类的内部结构(类的属性和操作)。类图是以类为中心来组织的,类图中的其他元素或属于某个类或与类相关联。
类图以反映类的结构(属性、操作)以及类之间的关系为主要目的,描述了软件系统的结构,是一种静态建模方法。类图中的“类”与面向对象语言中的“类”的概念是对应的,是对现实世界中的事物的抽象。
- 类图 Class Diagram:类图描述系统中类的静态结构。不仅定义系统中的类,表示类之间的联系如关联、依赖、聚合等,也包括类的内部结构(类的属性和操作)。
- 对象图(Object Diagram):对象图是类图的实例,几乎使用与类图完全相同的标识。他们的不同点在于对象图显示类的多个对象实例,而不是实际的类。
类图中的事物及解释
类
从上到下分为三部分,分别是类名、属性和操作。
- 类名是必须有的。
- 类如果有属性,则每一个属性都必须有一个名字,另外还可以有其它的描述信息,如可见性、数据类型、缺省值等。
- 类如果有操作,则每一个操作也都有一个名字,其它可选的信息包括可见性、参数的名字、参数类型、参数缺省值和操作的返回值的类型等。
类型:
- 接口:一组操作的集合,只有操作的声明而没有实现
- 抽象类:不能被实例化的类,一般至少包含一个抽象操作
- 模板类:一种参数化的类,在编译时把模板参数绑定到不同的数据类型,从而产生不同的类
在 UML 类图中表示具体类
具体类在类图中用矩形框表示,矩形框分为三层:
- 第一层是类名字;
- 第二层是类的成员变量;
- 第三层是类的方法。
成员变量以及方法前的访问修饰符用符号来表示:
UML 表示 | Java 修饰符 | Rose 工具中的表示 |
---|---|---|
+ | public | ![]() ![]() |
- | private | ![]() |
# | protected | ![]() |
~ | default :同一个包中的类才能访问的方法和字段 | |
不带符号 | default 或者不关心 | |
下划线/$ | static 静态方法、静态字段 |
在 UML 类图中表示抽象类
抽象类在 UML 类图中同样用矩形框表示,但是抽象类的类名以及抽象方法的名字都用斜体字表示。
例 1:
例 2:抽象类的 C++ 与 Java 实现
1 | class Vehicle |
1 | public abstract class Vehicle |
在 UML 类图中表示接口
接口在类图中也是用矩形框表示,但是与类的表示法不同的是,接口在类图中的第一层顶端用构造型 <<interface>>
表示,下面是接口的名字,第二层是方法,如图 3 所示。此外,接口还有另一种表示法,俗称棒棒糖表示法,就是类上面的一根棒棒糖(圆圈 + 实线)。圆圈旁为接口名称,接口方法在实现类中出现。
表示接口时,可以默认 abstract
和 public
。不需要斜体表示。
在 UML 类图中表示包
类和接口一般都出现在包中。
关系
注意,以上的类的关系分类方式仅供参考。在其他资料中可能会有以下说法,本文在此不再深究:
- 依赖关系、关联关系是平级的。依赖关系是一种独立的关系。
- 实现关系是泛化关系的子类型。
关联关系
关联关系描述了类的结构之间的关系。具有方向、名字、角色和多重性等信息。一般的关联关系语义较弱。也有两种语义较强,分别是聚合与组合。
关联关系(Association)是指对象和对象之间的连接,它使一个对象知道另一个对象的属性和方法。在 Java 中,关联关系的代码表现形式为一个对象含有另一个对象的引用。
关联关系有单向关联和双向关联。
- 双向关联:如果两个对象都知道(即可以调用)对方的公共属性和操作,那么二者就是双向关联。
- 单向关联:如果只有一个对象知道(即可以调用)另一个对象的公共属性和操作,那么就是单向关联。大多数关联都是单向关联,单向关联关系更容易建立和维护,有助于寻找可重用的类。
特性:
- 导航性(Navigatity):在 UML 图中,双向关联关系用带双箭头的实线或者无箭头的实线双线表示。单向关联用一个带箭头的实线表示,箭头指向被关联的对象。被指的人不知道有人关联它。
- 多重性(multipicity):一个对象可以持有其它对象的数组或者集合。在 UML 中,通过放置多重性(multipicity)表达式在关联线的末端来表示:
- 数字:精确的数量
0..
:表示 0 到多个0..1
:表示 0 或者 1 个,在 Java 中经常用一个空引用来实现1..*
:表示 1 到多个
可以在类名前面加上黑三角表示类之间的关联关系。
聚合关系
聚合(Aggregation)是关联关系的一种特例,是特殊的关联关系。它体现的是整体与部分的拥有关系,即 “has a” 的关系。此时整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享,所以聚合关系也常称为共享关系。
在 UML 图中,聚合关系用空心菱形加实线箭头表示,空心菱形在整体一方,箭头指向部分一方。
哪边是容器哪边就是菱形箭头,像个小篮子一样。
公司部门与员工的关系,一个员工可以属于多个部门,一个部门撤消了,员工可以转到其它部门。
组合关系
组合(Composition)也是关联关系的一种特例,是语义更强的聚合。它同样体现整体与部分间的包含关系,即 “contains a” 的关系。但此时整体与部分是不可分的,部分也不能给其它整体共享,作为整体的对象负责部分的对象的生命周期(部分和整体具有相同的生命周期)。这种关系比聚合更强,也称为强聚合。如果 A 组合 B,则 A 需要知道 B 的生存周期,即可能 A 负责生成或者释放 B,或者 A 通过某种途径知道 B 的生成和释放。
在 UML 图中,组合关系用实心菱形加实线箭头表示,实心菱形在整体一方,箭头指向部分一方。
组合和聚合代码没什么区别,两个对象之间的关系到底是聚合还是组合看你怎么认为。如果一定要区分,那么如果在删除整体对象的时候,必须删掉部分对象,那么就是组合关系,否则可能就是聚合关系。从业务角度上来看,如果作为整体的对象必须要部分对象的参与,才能完成自己的职责,那么二者之间就是组合关系,否则就是聚合关系。
人包含头、躯干、四肢,它们的生命周期一致。当人出生时,头、躯干、四肢同时诞生。当人死亡时,作为人体组成部分的头、躯干、四肢同时死亡。
汽车与轮胎,汽车作为整体,轮胎作为部分。如果用在二手车销售业务环境下,二者之间就是聚合关系。因为轮胎作为汽车的一个组成部分,它和汽车可以分别生产以后装配起来使用,但汽车可以换新轮胎,轮胎也可以卸下来给其它汽车使用。如果用在驾驶系统业务环境上,汽车如果没有轮胎,就无法完成行驶任务,二者之间就是一个组合关系。
再比如网上书店业务中的订单和订单项之间的关系,如果订单没有订单项,也就无法完成订单的业务,所以二者之间是组合关系。而购物车和商品之间的关系,因为商品的生命周期并不被购物车控制,商品可以被多个购物车共享,因此,二者之间是聚合关系。
组合关系,代码表现为 Dialog 的属性有 Button 和 TextBox 的对象。
组合关系,员工与时间卡
泛化关系
泛化关系(Generalization)是指对象与对象之间的继承关系。如果对象 A 和对象 B 之间的“is a”关系成立,那么二者之间就存在继承关系,对象 B 是父对象,对象 A 是子对象。比如:一个类(子类、子接口)继承另外一个类(称为父类、父接口)的功能。
面向对象:从底部、从具体开始抽象。思路,现从有的各种各样的子类,再到父类。
在 UML 类图中,泛化关系用空心三角和实线组成的箭头表示,从子类指向父类。关于箭头的方向理解:父类不知道子类的定义,子类一定知道父类的定义。只有在知道对方信息的时候才能指向对方,因此箭头方向是从子类指向父类。
在 Java 中就是 extends
关键字。
实现关系
实现关系是指接口及其实现类之间的关系。在 UML 类图中,实现关系用空心三角和虚线组成的箭头来表示,从实现类指向接口。
在 C++ 语言里面,使用抽象类代替接口,使用泛化关系代替实现关系。在 Java 语言里面,有相应的关键字 interface
、implements
。
例 1:
例 2:类 Circle、Rectangle 实现了接口 Shape 操作。
1 | class Shape{ |
1 | public interface Shape{ |
依赖关系
依赖(Dependency)关系是一种弱关联关系,描述了一个类的变化对依赖于它的类产生影响的情况。有多种表现形式,例如绑定(bind)、友元(friend)等。
如果对象 A 用到对象 B,但是和 B 的关系不是太明显的时候,就可以把这种关系看作是依赖关系。如果对象 A 依赖于对象 B,则 A “use a” B。比如驾驶员和汽车的关系,驾驶员使用汽车,二者之间就是依赖关系。
在 UML 类图中,依赖关系用一个带虚线的箭头表示,由使用方指向被使用方,表示使用方对象持有被使用方对象的引用。
A 依赖于 B 的表现形式:
- B 为 A 的局部变量或构造器
- B 作为 A 的方法或构造器的参数
- B 作为 A 的方法的返回值
- A 调用 B 的静态方法
代码清单 1 B.java:
1 | public class B { |
代码清单 2 A.java:
1 | public class A { |
例 1:绑定依赖
模板类 Stack<T>
定义了栈相关的操作;IntStack 将参数 T 与实际类型 int 绑定,使得所有操作都针对 int 类型的数据。
1 | template<typename T> |
1 | // 编译器生成 |
例 2:类 Memento 和类 Originator 建立了友元依赖关系,以便 Originator 使用 Memento 的私有变量 state。
类图案例
本节将配合研究生课程《系统建模与分析》类图相关案例演示。
图形编辑器
图形编辑器一般都具有一些基本图形,如直线、矩形等,用户可以直接使用基本图形画图,也可以把基本图形组合在一起创建复杂图形。如果区别对待基本图形和组合图形,会使代码变得复杂,而且多数情况下用户认为二者是一样的。组合模式可以用相同的方式处理两种图形。
- Graphics: 基本图形和组合图形的父类,声明了所有图形共同的操作,如 Draw;也声明了专用于组合图形管理子图形的操作,如 Add、Remove
- Line、Rectangle:基本图形类
- GroupGraphics:组合图形类,与父类有组合关系,从而可以组合所有图形对象(基本图形和组合图形)
演出售票系统
在用例驱动的开发过程中,通过分析各个用例及参与者得到类图。分析用例图的过程中需要根据面向对象的原则设计类和关系,根据用例的细节设计类的属性和操作。
在这里只考虑以下三个用例:
- Buy tickets:买个人票
- Buy Subscription:买套票
- Make charges:信用卡付款
本文 PlantUML 归档
1 | class Dialog{ |
1 | abstract class Vehicle{ |
1 | abstract class Account{ |
1 | class Stack<T>{ |
1 | abstract class Graphics { |
1 | @startuml |
1 | skinparam groupInheritance 2 |
1 | left to right direction |
1 | class Memento{ |
本文参考
- 30 分钟学会 UML 类图 - 知乎 (zhihu.com)
- 本科学习笔记《面向对象设计与设计模式》
- 研究生课程《系统建模与分析》相关课件