类图(Class Diagram)描述系统中类的静态结构。不仅定义系统中的类,表示类之间的联系如关联、依赖、聚合等,也包括类的内部结构(类的属性和操作)。类图是以类为中心来组织的,类图中的其他元素或属于某个类或与类相关联。

类图以反映类的结构(属性、操作)以及类之间的关系为主要目的,描述了软件系统的结构,是一种静态建模方法。类图中的“类”与面向对象语言中的“类”的概念是对应的,是对现实世界中的事物的抽象。

image.png

UML中的类图与对象图

  • 类图 Class Diagram:类图描述系统中类的静态结构。不仅定义系统中的类,表示类之间的联系如关联、依赖、聚合等,也包括类的内部结构(类的属性和操作)。
  • 对象图(Object Diagram):对象图是类图的实例,几乎使用与类图完全相同的标识。他们的不同点在于对象图显示类的多个对象实例,而不是实际的类。image.png
判断:类图是描述系统中类的静态结构,对象图是描述系统中类的动态结构。❌

类图中的事物及解释

image.png

从上到下分为三部分,分别是类名、属性和操作。

  • 类名是必须有的。
  • 类如果有属性,则每一个属性都必须有一个名字,另外还可以有其它的描述信息,如可见性、数据类型、缺省值等。
  • 类如果有操作,则每一个操作也都有一个名字,其它可选的信息包括可见性、参数的名字、参数类型、参数缺省值和操作的返回值的类型等。

类型:

  • 接口:一组操作的集合,只有操作的声明而没有实现
  • 抽象类:不能被实例化的类,一般至少包含一个抽象操作
  • 模板类:一种参数化的类,在编译时把模板参数绑定到不同的数据类型,从而产生不同的类

image.png

在 UML 类图中表示具体类

具体类在类图中用矩形框表示,矩形框分为三层:

  • 第一层是类名字;
  • 第二层是类的成员变量;
  • 第三层是类的方法。

成员变量以及方法前的访问修饰符用符号来表示:

UML 表示 Java 修饰符 Rose 工具中的表示
+ public image.png
image.png
- private image.png
# protected image.png
~ default:同一个包中的类才能访问的方法和字段
不带符号 default 或者不关心
下划线/$ static 静态方法、静态字段

在 UML 类图中表示抽象类

抽象类在 UML 类图中同样用矩形框表示,但是抽象类的类名以及抽象方法的名字都用斜体字表示。

例 1:

例 2:抽象类的 C++ 与 Java 实现

image.png

1
2
3
4
5
6
7
8
9
class Vehicle
{
public:
virtual int Start() = 0;
virtual int Stop() = 0;
virtual int Run(float fSpeed) = 0;
private:
float fMaxSpeed;
};
1
2
3
4
5
6
7
8
public abstract class Vehicle
{
public abstract int Start();
public abstract int Stop();
public abstract int Run(float fSpeed);

private float fMaxSpeed;
}

在 UML 类图中表示接口

接口在类图中也是用矩形框表示,但是与类的表示法不同的是,接口在类图中的第一层顶端用构造型 <<interface>> 表示,下面是接口的名字,第二层是方法,如图 3 所示。此外,接口还有另一种表示法,俗称棒棒糖表示法,就是类上面的一根棒棒糖(圆圈 + 实线)。圆圈旁为接口名称,接口方法在实现类中出现。

表示接口时,可以默认 abstractpublic 。不需要斜体表示。

在 UML 类图中表示包

类和接口一般都出现在包中。

关系

注意,以上的类的关系分类方式仅供参考。在其他资料中可能会有以下说法,本文在此不再深究:

  • 依赖关系、关联关系是平级的。依赖关系是一种独立的关系。
  • 实现关系是泛化关系的子类型。

关联关系

image.png

关联关系描述了类的结构之间的关系。具有方向、名字、角色和多重性等信息。一般的关联关系语义较弱。也有两种语义较强,分别是聚合与组合。

关联关系(Association)是指对象和对象之间的连接,它使一个对象知道另一个对象的属性和方法。在 Java 中,关联关系的代码表现形式为一个对象含有另一个对象的引用。

关联关系有单向关联和双向关联。

  • 双向关联:如果两个对象都知道(即可以调用)对方的公共属性和操作,那么二者就是双向关联。
  • 单向关联:如果只有一个对象知道(即可以调用)另一个对象的公共属性和操作,那么就是单向关联。大多数关联都是单向关联,单向关联关系更容易建立和维护,有助于寻找可重用的类。

特性:

  • 导航性(Navigatity):在 UML 图中,双向关联关系用带双箭头的实线或者无箭头的实线双线表示。单向关联用一个带箭头的实线表示,箭头指向被关联的对象。被指的人不知道有人关联它。
  • 多重性(multipicity):一个对象可以持有其它对象的数组或者集合。在 UML 中,通过放置多重性(multipicity)表达式在关联线的末端来表示:
    • 数字:精确的数量
    • 0..:表示 0 到多个
    • 0..1:表示 0 或者 1 个,在 Java 中经常用一个空引用来实现
    • 1..*:表示 1 到多个

可以在类名前面加上黑三角表示类之间的关联关系。

image.png

聚合关系

image.png

聚合(Aggregation)是关联关系的一种特例,是特殊的关联关系。它体现的是整体与部分的拥有关系,即 “has a” 的关系。此时整体与部分之间是可分离的,它们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享,所以聚合关系也常称为共享关系

在 UML 图中,聚合关系用空心菱形加实线箭头表示,空心菱形在整体一方,箭头指向部分一方

哪边是容器哪边就是菱形箭头,像个小篮子一样。

聚合关系案例

公司部门与员工的关系,一个员工可以属于多个部门,一个部门撤消了,员工可以转到其它部门。

组合关系

image.png

组合(Composition)也是关联关系的一种特例,是语义更强的聚合。它同样体现整体与部分间的包含关系,即 “contains a” 的关系。但此时整体与部分是不可分的,部分也不能给其它整体共享,作为整体的对象负责部分的对象的生命周期(部分和整体具有相同的生命周期)。这种关系比聚合更强,也称为强聚合。如果 A 组合 B,则 A 需要知道 B 的生存周期,即可能 A 负责生成或者释放 B,或者 A 通过某种途径知道 B 的生成和释放。

在 UML 图中,组合关系用实心菱形加实线箭头表示,实心菱形在整体一方,箭头指向部分一方

组合和聚合代码没什么区别,两个对象之间的关系到底是聚合还是组合看你怎么认为。如果一定要区分,那么如果在删除整体对象的时候,必须删掉部分对象,那么就是组合关系,否则可能就是聚合关系。从业务角度上来看,如果作为整体的对象必须要部分对象的参与,才能完成自己的职责,那么二者之间就是组合关系,否则就是聚合关系。

生活中的组合关系

人包含头、躯干、四肢,它们的生命周期一致。当人出生时,头、躯干、四肢同时诞生。当人死亡时,作为人体组成部分的头、躯干、四肢同时死亡。

汽车与轮胎,汽车作为整体,轮胎作为部分。如果用在二手车销售业务环境下,二者之间就是聚合关系。因为轮胎作为汽车的一个组成部分,它和汽车可以分别生产以后装配起来使用,但汽车可以换新轮胎,轮胎也可以卸下来给其它汽车使用。如果用在驾驶系统业务环境上,汽车如果没有轮胎,就无法完成行驶任务,二者之间就是一个组合关系。

再比如网上书店业务中的订单和订单项之间的关系,如果订单没有订单项,也就无法完成订单的业务,所以二者之间是组合关系。而购物车和商品之间的关系,因为商品的生命周期并不被购物车控制,商品可以被多个购物车共享,因此,二者之间是聚合关系。

UML 示例

组合关系,代码表现为 Dialog 的属性有 Button 和 TextBox 的对象。
image.png

组合关系,员工与时间卡

泛化关系

image.png

泛化关系(Generalization)是指对象与对象之间的继承关系。如果对象 A 和对象 B 之间的“is a”关系成立,那么二者之间就存在继承关系,对象 B 是父对象,对象 A 是子对象。比如:一个类(子类、子接口)继承另外一个类(称为父类、父接口)的功能。

面向对象:从底部、从具体开始抽象。思路,现从有的各种各样的子类,再到父类。

在 UML 类图中,泛化关系用空心三角和实线组成的箭头表示,从子类指向父类。关于箭头的方向理解:父类不知道子类的定义,子类一定知道父类的定义。只有在知道对方信息的时候才能指向对方,因此箭头方向是从子类指向父类。

在 Java 中就是 extends 关键字。

代码与类图案例

image.png

实现关系

实现关系是指接口及其实现类之间的关系。在 UML 类图中,实现关系用空心三角和虚线组成的箭头来表示,从实现类指向接口

在 C++ 语言里面,使用抽象类代替接口,使用泛化关系代替实现关系。在 Java 语言里面,有相应的关键字 interfaceimplements

例 1:

例 2:类 Circle、Rectangle 实现了接口 Shape 操作。

image.png

1
2
3
4
5
6
7
8
9
10
11
12
class Shape{ 
public:
virtual void Draw() = 0;
};

class Circle : public Shape{
public:
void Draw();
private;
Point ptCenter;
int nRadius;
};
1
2
3
4
5
6
7
8
9
10
public interface Shape{ 
public abstract void Draw();
}

public class Circle implements Shape
{
public void Draw();
private Point ptCenter;
private int nRadius;
}

依赖关系

依赖(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
2
3
4
5
6
7
8
9
10
11
public class B {
public String field1; //成员变量

public void method1() {
System.println("在类B的方法1中");
}

public static void method2() { //静态方法
System.out.println("在类B的静态方法2中");
}
}

代码清单 2 A.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class A {
public void method1() {
//A依赖于B的第一种表现形式:B为A的局部变量
B b = new B();
b.method1();
}

public void method2() {
//A依赖于B的第二种表现形式: 调用B的静态方法
B.method2();
}

public void method3(B b) {
//A依赖于B的第三种表现形式:B作为A的方法参数
String s = b.field1;
}

//A依赖于B的第四种表现形式:B作为A的方法的返回值
public B method4() {
return new B();
}
}

例 1:绑定依赖

模板类 Stack<T> 定义了栈相关的操作;IntStack 将参数 T 与实际类型 int 绑定,使得所有操作都针对 int 类型的数据。

image.png

1
2
3
4
5
6
7
8
9
10
11
12
template<typename T>
class Stack
{
private:
int size;
public:
int Push(T elem);
int Pop();
const T& GetTop();
};

typedef Stack<float> FloatStack;
1
2
3
4
5
6
7
8
9
10
// 编译器生成
class FloatStack
{
private:
int size;
public:
int Push(float elem);
int Pop();
const float& GetTop();
};

例 2:类 Memento 和类 Originator 建立了友元依赖关系,以便 Originator 使用 Memento 的私有变量 state。

image.png

类图案例

本节将配合研究生课程《系统建模与分析》类图相关案例演示。

图形编辑器

图形编辑器一般都具有一些基本图形,如直线、矩形等,用户可以直接使用基本图形画图,也可以把基本图形组合在一起创建复杂图形。如果区别对待基本图形和组合图形,会使代码变得复杂,而且多数情况下用户认为二者是一样的。组合模式可以用相同的方式处理两种图形。

image.png

  • Graphics: 基本图形和组合图形的父类,声明了所有图形共同的操作,如 Draw;也声明了专用于组合图形管理子图形的操作,如 Add、Remove
  • Line、Rectangle:基本图形类
  • GroupGraphics:组合图形类,与父类有组合关系,从而可以组合所有图形对象(基本图形和组合图形)

演出售票系统

在用例驱动的开发过程中,通过分析各个用例及参与者得到类图。分析用例图的过程中需要根据面向对象的原则设计类和关系,根据用例的细节设计类的属性和操作。

image.png

在这里只考虑以下三个用例:

  • Buy tickets:买个人票
  • Buy Subscription:买套票
  • Make charges:信用卡付款

image.png

本文 PlantUML 归档

1
2
3
4
5
6
7
8
9
class Dialog{
- btnOK:Button
- btnCancel: Button
- txtInfo: TextBox
}
class Button
class TextBox
Dialog *-- Button
Dialog *-- TextBox
1
2
3
4
5
6
abstract class Vehicle{
- fMaxSpeed:float
+ Start():int
+ Stop():int
+ Run(float fSpeed):int
}
1
2
3
4
5
6
7
8
9
abstract class Account{
- balance: double = 1
+ Deposit(amount:double) : int
+ {abstract} ComputeInterest() : double
}
class SavingsAccount{
+ ComputeInterset():double
}
Account<|--SavingsAccount
1
2
3
4
5
6
7
8
9
10
class Stack<T>{ 
- size:int
+ Push(elem:T): int
+ Pop():int
+ GetTop(): const T &
}
class IntStack
class FloatStack
Stack<..IntStack:<<bind>>
Stack<..FloatStack:<<bind>>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
abstract class Graphics {
{abstract} +Draw()
{abstract} +Add(g:Graphics)
{abstract} +Remove(g:Graphics)
{abstract} +GetChild(index:int)
}

class Line{
+Draw()
}

class Rectangle{
+Draw()
}

class GroupGraphics{
+Draw()
+Add(g:Graphics)
+Remove(g:Graphics)
+GetChild(index:int)
}
skinparam classAttributeIconSize 0
skinparam groupInheritance 3

Graphics <|-- Line
Graphics <|-- Rectangle
Graphics <|-- GroupGraphics
Graphics --*GroupGraphics: + children
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@startuml
left to right direction

rectangle BoxOffice {

usecase (Buy Subscription) as bs
usecase (Make charges) as mc
usecase (Buy tickets) as bt
usecase (Survey sales) as ss
}
actor 信息亭 as xxt
actor clerk
actor 监督员 as jdy
actor 信用卡服务商 as fws

xxt-->bt
bt <-- clerk
bs <-- clerk
mc<-bt:<<include>>
mc<-bs:<<include>>
ss<--jdy
mc<--fws
@enduml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
skinparam groupInheritance 2
class Customer{
- name: string
- phone: string
}
class Reservation{
- date: Date
}
class SubscriptionSeries{
- series: int
}
class IndividualReservation{}
class Ticket{
- no:int
}
Customer "1" -- "1..*" Reservation
Reservation <|-- SubscriptionSeries

Reservation <|-- IndividualReservation
note left on link
使用泛化关系处理客户
购买个人票和套票两种不同情况
end note
SubscriptionSeries "0..1" -- "3..6" Ticket
IndividualReservation "0..1" -- "1" Ticket

class kiosk{
+ ShowAvailability(seatlist)
+ DemandPayment(cost)
+ PrintTickets(performance, seats)
+ EjectCard()
}
class CreditCardService{
+ Charge(cardnumber,cost)
}
class BoxOffice{
+ Request(count, performance)
+ Select(seats)
+ InsertCard(cardnumber, password)
+ Authorized()
}
class Performance{
- no:int
- date:Date
- time:Time
}
class Seat{
- no:int
- colomn:int
- row:int
- available:bool
}
kiosk--BoxOffice
CreditCardService--BoxOffice
BoxOffice--Performance
Seat "0..*" -- "1" Performance
Ticket "1" -- "1" Seat
1
2
3
4
5
6
7
8
9
10
left to right direction
class Client
Client --> Target : Uses >
note right of Target: Client uses Target

Factory --> Product : Creates >
note right of Product: Factory creates Product

Subject --> Observer : Notifies >
note right of Observer: Subject notifies Observer
1
2
3
4
5
6
class Memento{
- state:State*
}
class Originator

Memento<..Originator:<<friend>>

本文参考