跳转到内容

绘制UML类图

类图用于描述系统中所包含的类以及它们之间的相互关系,帮助人们简化对系统的理解,它是系统分析和设计阶段的重要产物,也是系统编码和测试的重要模型依据。

类图

类图分为上中下三部分:上层是类名,中间层是属性(类的成员变量),下层是方法(类的成员函数)。

可见性:+ 表示public# 表示protected- 表示private__(下划线)表示static

属性的表示方式:【可见性】【属性名称】:【类型】= { 缺省值,可选 }

方法的表示方式:【可见性】【方法名称】(【参数名 : 参数类型,……】):【返回值类型】

如果我们定义的类是一个抽象类(类中有纯虚函数),在画UML类图的时候,类名需要使用斜体显示。

虚成员函数使用斜体表示;纯虚函数除使用斜体表示外在函数最后指定=0

例子 猎人(Hunter)类

#include <iostream>
#include <string>
class Hunter {
public:
int m_age{ 32 };
static int m_times;
std::string getName() { return m_name; }
void setName(std::string name) { m_name = std::move(name); }
void goHunting();
static void saySorry();
protected:
std::string m_name{ "Jack" };
void aiming();
private:
std::string m_gunName{ "AK-47" };
void shoot();
};
classDiagram
class Hunter {
+m_age : int=32
+m_times : int$
+getName() string
+setName(name : string) void
+goHunting() void
+saySorry() void$
#m_name : string = "Jack"
#aiming() void
-m_gunName : string = "AK-47"
-shoot() void
}

例子 鸟(Bird)类

class Bird {
public:
std::string m_name{ "DuJuan" };
virtual std::string getName() = 0;
virtual void setName(std::string name) = 0;
};
classDiagram
class Bird {
<<Abstract>>
+m_name : string = "DuJuan"
+getName() string = 0*
+setName(name : string) void = 0*
}

完整代码:

#include <iostream>
#include <string>
class Hunter {
public:
int m_age{32};
static int m_times;
std::string getName() { return m_name; }
void setName(std::string name) { m_name = std::move(name); }
void goHunting();
static void saySorry();
protected:
std::string m_name{"Jack"};
void aiming();
private:
std::string m_gunName{"AK-47"};
void shoot();
};
class Bird {
public:
std::string m_name{"DuJuan"};
virtual std::string getName() = 0;
virtual void setName(std::string name) = 0;
};
int Hunter::m_times{3};
void Hunter::goHunting() {
aiming();
shoot();
}
void Hunter::saySorry() {
std::string count = std::to_string(m_times);
std::cout << "Say sorry to every animal " << count << " times!" << std::endl;
}
void Hunter::aiming() {
std::cout << "使用" + m_gunName + "瞄准猎物..." << std::endl;
}
void Hunter::shoot() {
std::cout << "使用" + m_gunName + "射击猎物..." << std::endl;
}
int main() {
Hunter h;
std::cout << h.getName() << std::endl;
}

例子 UML类图复杂例子

classDiagram
Animal <|-- Duck
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run()
}
classDiagram
note "From Duck till Zebra"
Animal <|-- Duck
note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run()
}

类之间的关系

继承关系(Inheritance)

继承也叫作泛化(Generalization),用于描述父子类之间的关系,父类又称为基类或者超类,子类又称作派生类。

在UML中,泛化关系用带空心三角形的实线来表示。有空心三角的一端指向父类,另一端连接子类

继承关系一共有两种:普通继承和抽象继承,不论哪一种,继承关系的线的样式是一样的。

class Bird {
public:
std::string getName() { return m_name; }
void setName(std::string name) { m_name = name; }
virtual void fly() = 0;
virtual void eat() = 0;
protected:
std::string m_sex;
std::string m_name;
};
class Cuckoo : public Bird {
public:
void fly() override { std::cout << "拍打翅膀飞行..." << std::endl; }
void eat() override { std::cout << "吃虫子..." << std::endl; }
};
class Eagle : public Bird {
public:
void fly() override { std::cout << "展翅翱翔..." << std::endl; }
void eat() override { std::cout << "吃小动物..." << std::endl; }
};
classDiagram
Bird <|-- Cuckoo
Bird <|-- Eagle
class Bird {
<<Abstract>>
#m_sex : string
#m_name : string
+getName() string
+setName(name : string) void
+fly() void = 0*
+eat() void = 0*
}
class Cuckoo {
+fly() void
+eat() void
}
class Eagle {
+fly() void
+eat() void
}

完整代码:

#include <iostream>
#include <string>
class Bird {
public:
std::string getName() { return m_name; }
void setName(std::string name) { m_name = name; }
virtual void fly() = 0;
virtual void eat() = 0;
protected:
std::string m_sex;
std::string m_name;
};
class Cuckoo : public Bird {
public:
void fly() override { std::cout << "拍打翅膀飞行..." << std::endl; }
void eat() override { std::cout << "吃虫子..." << std::endl; }
};
class Eagle : public Bird {
public:
void fly() override { std::cout << "展翅翱翔..." << std::endl; }
void eat() override { std::cout << "吃小动物..." << std::endl; }
};
int main() {
Cuckoo c;
c.eat();
Eagle e;
e.eat();
return 0;
}

关联关系(Association)

关联(Assocition)关系是类与类之间最常见的一种关系,它是一种结构化的关系,表示一个对象与另一个对象之间有联系,如汽车和轮胎、师傅和徒弟、班级和学生等。

在UML类图中,用(带箭头或不带箭头的)实线连接有关联关系的类。在C++的类中,这种关联关系通常体现为,将一个类的对象作为另一个类的成员变量。

类之间的关联关系有三种,分别是:单向关联、双向关联、自关联。

单向关联

class Parent {};
class Child {
private:
Parent m_father;
};
classDiagram
Parent <-- Child
class Child {
-m_father : Parent
}

这个例子演示了单向关联,使用的连接线是带单向箭头的实线

哪个类作为了当前类的成员变量,那么箭头就指向哪个类。

在这个例子中 Parent 类作为了 Child 类的成员变量,因此箭头端指向 Parent 类,另一端连接 Child 类。

双向关联

class Parent {
private:
Child m_son;
};
class Child {
private:
Parent m_father;
};
classDiagram
Parent -- Child
class Parent {
-m_son : Child
}
class Child {
-m_father : Parent
}

在画UML类图的时候,一般使用没有箭头的实线来连接有双向关联关系的两个类。这两个类的对象分别作为了对方类的成员变量。

注:有些UML绘图软件使用带双向箭头的实线来表示双向关联关系。

自关联

自关联指当前类中包含一个自身类型的对象成员,这在链表中非常常见。

单向链表中都会有一个指向自身节点类型的后继指针成员。而双向链表中会包含一个指向自身节点类型的前驱指针和一个指向自身节点类型的后继指针。

一般使用带箭头的实线来描述自关联关系,我中有我,独角戏。

双向链表节点类:

class Node {
private:
void* m_data;
Node* m_prev;
Node* m_next;
};
classDiagram
Node <-- Node
class Node {
-m_data : void*
-m_prev : Node*
-m_next : Node*
}

聚合关系(Aggregation)

聚合(Aggregation)关系表示整体与部分的关系。

在聚合关系中,成员对象是整体的一部分,但是成员对象可以脱离整体对象独立存在。

例子:

  • 汽车(Car)与 引擎(Engine)、轮胎(Wheel)、车灯(Light)
  • 森林(Forest)与 植物(Plant)、动物(Animal)、水(Water)、阳光(Sunshine)

在UML中,聚合关系用带空心菱形的直线表示。

表示聚合关系的线,有空心菱形的一端指向整体对象,另一端连接局部对象(有些UML绘图软件在这一端还带一个箭头)。

用代码实现聚合关系时,成员对象通常以构造方法、Setter方法的方式注入到整体对象之中,因为成员对象可以脱离整体对象独立存在。

森林(Forest)类:

class Plant {}; // 植物
class Animal {}; // 动物
class Water {}; // 水
class Sunshine {}; // 阳光
// 森林
class Forest {
public:
Forest(Plant p, Animal a, Water w, Sunshine s) :
m_plant{p}, m_animal{a}, m_water{w}, m_sun{s} {}
private:
Plant m_plant;
Animal m_animal;
Water m_water;
Sunshine m_sun;
};
classDiagram
Forest o-- Plant
Forest o-- Animal
Forest o-- Water
Forest o-- Sunshine
class Forest {
-m_plant : Plant
-m_animal : Animal
-m_water : Water
-m_sun : Sunshine
}

组合关系(Composition)

组合(Composition)关系也表示的是一种整体和部分的关系,但是在组合关系中整体对象可以控制成员对象的生命周期,一旦整体对象不存在,成员对象也不存在。整体对象和成员对象之间同生共死。

例子:

  • 头(Head)和 嘴巴(Mouth)、鼻子(Nose)、耳朵(Ear)、眼睛(Eye)
  • 树(Tree)和 树根(Root)、树干(Trunk)、树枝(Branch)、树叶(Leaf)

在UML中组合关系用带实心菱形的直线表示。

树(Tree)类:

class Root {}; // 树根
class Trunk {}; // 树干
class Branch {}; // 树枝
class Leaf {}; // 树叶
// 树
class Tree {
public:
Tree() {
m_root = new Root;
m_trunk = new Trunk;
m_branch = new Branch;
m_leaf = new Leaf;
}
~Tree() {
delete m_root;
delete m_trunk;
delete m_branch;
delete m_leaf;
}
private:
Root* m_root;
Trunk* m_trunk;
Branch* m_branch;
Leaf* m_leaf;
};
classDiagram
Tree *-- Root
Tree *-- Trunk
Tree *-- Branch
Tree *-- Leaf
class Tree {
-m_root : Root*
-m_trunk : Trunk*
-m_branch : Branch*
-m_leaf : Leaf*
}

依赖关系(Dependency)

依赖(Dependency)关系是一种使用关系,特定事物的改变有可能会影响到使用该事物的其他事物。

在需要表示一个事物使用另一个事物时使用依赖关系。

大多数情况下,依赖关系体现为,在某个类的方法中,使用另一个类的对象作为参数。

例子:

  • 驾驶员(Driver)开车,需要将车(Car)对象作为参数传递给 Driver 类的drive()方法。
  • 树木(Tree)的生长,需要将空气(Air)、水(Water)、土壤(Soil)对象作为参数传递给 Tree 类的 grow()方法。

依赖关系通常通过三种方式来实现:

  • 将一个类的对象作为另一个类中方法的参数
  • 在一个类的方法中将另一个类的对象作为其对象的局部变量
  • 在一个类的方法中调用另一个类的静态方法

在UML中,依赖关系用带箭头的虚线表示,由依赖的一方指向被依赖的一方

class Car {
public:
void move() {}
};
class Driver {
public:
void drive(Car car) {
car.move();
}
};
classDiagram
Driver ..> Car
class Car {
+move() void
}
class Driver {
+drive(car : Car) void
}
class Water {};
class Air {};
class Soil {};
class Tree {
public:
void grow(Water w, Air a, Soil s) {
cout << "借助 w 中的水分, s 中的养分和 a 中的二氧化碳, 我就可以茁壮成长了";
}
};
classDiagram
Tree ..> Water
Tree ..> Air
Tree ..> Soil
class Tree {
+grow(w : Water, a : Air, s : Soil) void
}

类之间的关系辨析

类之间的关系强弱顺序:继承(泛化) >  组合  >  聚合  >  关联  >  依赖。

关联关系、聚合关系、组合关系之间的区别:

  • 关联和聚合的区别主要在语义上:关联的两个对象之间一般是平等的,聚合则一般是不平等的。
  • 聚合和组合的区别则在语义和实现上都有差别:
    • 组合的两个对象之间生命周期有很大的关联。被组合的对象,在组合对象创建的同时或者创建之后创建,在组合对象销毁之前销毁。聚合则无需考虑这些事情。
    • 一般来说被组合对象不能脱离组合对象独立存在,而且也只能属于一个组合对象,聚合则不一样,被聚合的对象可以属于多个聚合对象。

例子:

  • 朋友之间属于关联关系,因为这种关系是平等的,关联关系只是用于表示两个对象之间的一种简单的联系而已。
  • 图书馆看书的时候,人和书属于聚合关系。书是可以独立存在的,而且书不仅可以属于自己,也可以属于别人。
  • 人和自己的心脏属于组合关系,因为心脏不能脱离人体而独自存在。

实际应用中,这三种关系的界限划分其实没有那么清楚,有些时候我们会感觉组合和聚合没什么区别,所以,在设计的时候没必要死抠细节,只要能够利用对象之间的关系设计出可行的解决方案即可。

参考