02. UML类图
一、什么是UML类图
UML 类图(Unified Modeling Language Class Diagram)是 UML(统一建模语言)中的一种图,用于描述系统中类的静态结构,包括类、接口、以及它们之间的关系,如继承、关联、依赖等。
UML 类图的主要元素有:
- 类(Class) :表示具有相同属性、操作、关系的对象集合。在类图中,类通常用一个带有分隔区域的矩形表示,分隔区域的上半部分显示类的名称,下半部分包含类的属性(字段)和操作(方法)。
- 接口(Interface) :定义了一组操作的契约,但没有提供这些操作的具体实现。接口在类图中通常使用带有尖括号的矩形表示。
- 关系 :
- 继承(Inheritance) :表示一个类(子类或派生类)继承另一个类(父类或基类)的属性和操作。这通过带有空心三角形的实线表示,箭头指向父类。
- 关联(Association) :表示类之间的某种连接。关联可以是双向的,也可以是单向的。关联关系用实线表示,如果类之间存在数量上的关系,还会在关联线上标注。
- 聚合(Aggregation) :是关联关系的一种特例,表示 “整体-部分” 的关系,即一个类是另一个类的组成部分。聚合关系用带有空心菱形的实线表示,菱形指向整体类。
- 组合(Composition) :也是关联关系的一种特例,表示强烈的 “整体-部分” 关系,其中部分类的生命周期依赖于整体类。组合关系用带有实心菱形的实线表示。
- 依赖(Dependency) :表示一个类依赖于另一个类的定义。这通常发生在当一个类使用到另一个类的实例作为参数,或者其方法涉及到另一个类的静态方法时。依赖关系用虚线加箭头表示。
二、类的UML图示
类(Class)封装了数据和行为,是面向对象的重要组成部分,它是具有相同属性、操作、关系的对象集合的总称。在系统中,每个类都具有一定的职责。职责指的是类要完成什么样的功能,要承担什么样的义务。一个类可以有多种职责,设计得好的类一般只有一种职责。在定义类的时候,将类的职责分解成为类的属性和操作(即方法)。类的属性即类的数据职责,类的操作即类的行为职责。
#include <iostream>
using namespace std;
class Person
{
private:
string name;
int age;
public:
Person(string name, int age);
void info(void);
string getName(void);
void setName(string name);
int getAge(void);
void setAge(int age);
};
Person::Person(string name, int age) : name(name), age(age) {}
void Person::info(void)
{
cout << "{姓名:" << name << ",年龄:" << age << endl;
}
string Person::getName(void)
{
return name;
}
void Person::setName(string name)
{
this->name = name;
}
int Person::getAge(void)
{
return age;
}
void Person::setAge(int age)
{
this->age = age;
}
类图(Class Diagram)是用出现在系统中的不同类来描述系统的静态结构,主要用来描述不同的类以及它们之间的关系。在 UML 中,类一般由三部分组成: 类名、属性 和 操作。
【1】、类名
每个类都必须有一个名字,类名是一个字符串。
【2】、属性
属性是指类的性质,即类的成员变量。一个类可以有任一多个属性,也可以没有属性。UML 规定属性的表示方式为:
可见性 名称 : 类型 [= 默认值]
- 可见性:表示该属性对于类外的元素而言是否可见,包含 公有(public)、私有(private)和 受保护(protected)3 种,在类图中分别使用符号
+
、-
和#
表示。 - 名称:表示属性名,用一个字符串表示。
- 类型:表示属性的数据类型、可以是基本数据类型、也可以是用于自定义类型。
- 默认值:是一个可选项,即属性的初始值。
【3】、操作
操作是类的任意一个实例对象都可以使用的行为,是类的成员方法。UML 规定操作的表示方法为:
可见性 名称([参数列表]) [: 返回类型]
- 可见性:表示该属性对于类外的元素而言是否可见,包含 公有(public)、私有(private)和 受保护(protected)3 种,在类图中分别使用符号
+
、-
和#
表示。 - 名称:表示方法名,用一个字符串表示。
- 参数列表:表示方法的参数,参数个数可以是任意的,多个参数之间使用逗号(,)隔开。
- 返回类型:是一个可选项,表示方法的返回值类型,可以是基本数据类型、也可以是用于自定义类型,还可以是空类型(void)如果是构造方法,则无返回类型。
三、类之间的关系
3.1、关联关系
关联关系(Association)是类与类之间最常用的一种关系,它是一种 结构化关系,用于表示 一类对象与另一类对象之间有联系,如汽车和轮胎、师傅和徒弟、班级和学生等。
在 UML 类图中,用 实线 连接有 关联关系 的对象所对应的类,在使用编程语言实现关联关系时,通常将一个类的对象作为另一个类的成员变量。在使用类图表示关联关系时可以在关联线上标注角色名,一般使用一个表示二者之间关系的动词或者名词表示角色名(有时该名词为实例对象名),关系的两端代表两种不同的角色。因此,在一个关联关系中可以包含两个角色名,角色名不是必需的,可以根据需要增加,其目的是使类之间的关系更加明确。
【1】、双向关联
默认情况下,关联是双向的。例如,顾客(Customer)购买商品(Product)并拥有商品,反之,卖出的商品总有某个顾客与之相关联。因此,Customer 类和 Product 类之间具有双向关联关系。
class Husband
{
private:
Wife wife;
};
class Wife
{
private:
Husband husband;
};
【2】、单向关联
类的关联关系也可以是单向的,在 UML 中 单向关联 用 带箭头的实线 表示。例如,顾客(Customer)拥有地址(Address),则 Customer 类与 Address 类具有单向关联关系。
class Address
{
};
class Customer
{
private:
Address address;
};
【3】、自关联
在系统中可能会存在一些类的属性对象类型为该类本身,这种特殊的关联关系称为 自关联。例如,一个节点类(Node)的成员又是节点 Node 类型的对象。
class Node
{
private:
Node * next;
};
【4】、多重性关联
多重性关联关系 又称为 重数性关联关系(Multiplicity),表示 两个关联对象在数量上的对应关系。在 UML 中,对象之间的多重性可以直接在关联直线上用一个数字或一个数字范围表示。
表示方法 | 说明 |
---|---|
1..1 | 表示另一个类的一个对象只与该类的一个对象有关系 |
0..* | 表示另一个类的一个对象与该类的零个或多个对象有关系 |
1..* | 表示另一个类的一个对象与该类的一个或多个对象有关系 |
0..1 | 表示另一个类的一个对象没有或只与该类的一个对象有关系 |
m..n | 表示另一个类的一个对象与该类对象最少 m,最多 n 个对象有关系(m≤n) |
class Product
{
};
class Customer
{
private:
Product product[10];
};
【5】、聚合关系
聚合关系(Aggregation)表示整体与部分的关系。在聚合关系中,成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在。在 UML 中,聚合关系 用 带空心菱形的直线 表示。例如,汽车发动机(Engine)是汽车(Car)的组成部分,但是汽车发动机可以独立存在,因此,汽车和发动机是聚合关系。
class Engine
{
};
class Car
{
private:
Engine engine;
public:
// 构造注入
Car(Engine engine);
// 设值注入
void setEngine(Engine engine);
};
Car::Car(Engine engine) : engine(engine) {}
void Car::setEngine(Engine engine)
{
this->engine = engine;
}
【6】、组合关系
组合关系(Composition)也 表示类之间整体和部分的关系,但是在组合关系中整体对象可以控制成员对象的生命周期。一旦整体对象不存在,成员对象也将不存在,成员对象与整体对象之间具有同生共死的关系。在 UML 中,组合关系 用 带实心菱形的直线 表示。例如,人的头(Head)与嘴巴(Mouth),嘴巴是头的组成部分之一,而且如果头没了,嘴巴也就没了,因此头和嘴巴是组合关系。
class Mouth
{
};
class Head
{
private:
Mouth *mouth;
public:
Head(void);
};
Head::Head(void)
{
mouth = new Mouth();
}
3.2、依赖关系
依赖关系(Dependency)是一种使用关系,特定事物的改变有可能会影响到使用该事物的其他事物,在需要表示 一个事物使用另一个事物 时使用依赖关系。大多数情况下,依赖关系体现在某个类的方法使用另一个类的对象作为参数。在 UML 中,依赖关系 用 带箭头的虚线表示,由依赖的一方指向被依赖的一方。
例如,驾驶员开车,在 Driver 类的 drive() 方法中将 Car 类型的对象 car 作为一个参数传递,以便在 drive() 方法中能够调用 Car 类的 move()方法,且驾驶员的 drive() 方法依赖车的 move() 方法,因此类 Driver 依赖类 Car。
在系统实施阶段,依赖关系通常通过 3 种方式来实现:
- 第 1 种也是最常用的一种方式,将 一个类的对象作为另一个类中方法的参数。
- 第 2 种方式是 在一个类的方法中将另一个类的对象作为其局部变量。
- 第 3 种方式是 在一个类的方法中调用另一个类的静态方法。
class Car
{
public:
void move(void);
};
void Car::move(void)
{
}
class Driver
{
public:
void drive(Car car);
};
void Driver::drive(Car car)
{
car.move();
}
3.3、泛化关系
泛化关系(Generalization)也就是 继承关系,用于 描述父类与子类之间的关系,父类又称作基类或超类,子类又称作派生类。在UML中,泛化关系 用 带空心三角形的直线 来表示。在代码实现时,使用面向对象的 继承机制 来实现 泛化关系。
例如,Student 类和 Teacher 类都是 Person 类的子类,Student 类和 Teacher 类继承了 Person 类的属性和方法,Person 类的属性包含姓名(name)和年龄(age),每一个 Student 和 Teacher 也都具有这两个属性。另外 Student 类增加了属性学号(studentNo),Teacher 类增加了属性教师编号(teacherNo),Person 类的方法包括行走 move() 和说话 say(),Student 类和 Teacher 类继承了这两个方法,而且 Student 类还新增方法 study(),Teacher 类新增方法teach()。在接口中,通常没有属性,而且所有的操作都是抽象的,只有操作的声明,没有操作的实现。UML中用与类的表示法类似的方式表示接口
#include <iostream>
using namespace std;
// 父类
class Person
{
private:
string name;
int age;
public:
void move(void);
void say(void);
};
void Person::move(void)
{
}
void Person::say(void)
{
}
// 子类
class Student : public Person
{
private:
string studentNo;
public:
void study(void);
};
void Student::study(void)
{
}
// 子类
class Teacher : public Person
{
private:
string teacherNo;
public:
void teach(void);
};
void Teacher::teach(void)
{
}
3.4、实现关系
在接口中,通常没有属性,而且所有的操作都是抽象的,只有操作的声明,没有操作的实现。UML 中用与类的表示法类似的方式表示接口。接口之间也可以有与类之间关系类似的继承关系和依赖关系,但是接口和类之间还存在一种实现(Realization)关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的操作。在 UML 中,类与接口之间的 实现关系 用 带空心三角形的虚线 来表示。
例如,定义了一个交通工具接口 Vehicle,包含一个抽象操作 move(),在类 Ship 和类 Car 中都实现了该 move() 操作,不过具体的实现细节将会不一样。
// 接口
class Vehicle
{
public:
virtual void move(void) = 0;
};
// 实现类
class Ship : public Vehicle
{
public:
void move(void) override;
};
void Ship::move(void)
{
}
// 实现类
class Car : public Vehicle
{
public:
void move(void) override;
};
void Car::move(void)
{
}