UML_类图
UML类图
统一建模语言(Unified Modeling Language,UML)是一种为面向对象系统的产品进行说明、可视化和编制文档的一种标准语言。UML是面向对象设计的建模工具,独立于任何具体程序设计语言。
UML图,包括用例图、协作图、活动图、序列图、部署图、构件图、类图、状态图等,本节记录UML类图。
类和类之间的6种关系:依赖、关联、聚合、组合、实现和继承。各种关系的强弱顺序:继承 = 实现 > 组合 > 聚合 > 关联 > 依赖。
类的表示
使用一个矩形框表示类,最上边是类名,中间是类的属性,最下面是类的成员方法。其中,属性和方法的访问权限:public用+表示,private用-表示,protected用#表示。
类与类之间的六种关系
下面分别总结类与类之间的6种关系。
依赖 Dependency
一个类使用另一个类的功能,但不拥有它。类与类之间的依赖关系通常表现为一个类使用了另一个类的成员或者作为参数、返回类型等。
A类使用了B类的一部分属性和方法,不会主动改变B类中的内容,是一种临时性的关联,具体表现在三个方面:
• 类A把类B的实例作为方法里的参数使用;
• 类A的某个方法里使用了类B的实例作为局部变量
• 类A调用了类B的静态方法
• 作为返回值依赖,即在一个类中的某个方法中,创建了另一个类并返回。比如在工厂模式中,C_MechanicFactory类中返回了 C_Mechanic,他们之间是依赖关系。
例子
有一个Student类,有一个教室类,教师类中使用vector存放多个学生,然后在教室类的addStudent方法中,将Student作为参数传入,并将其添加到教室数组中vec中,这种关系就是依赖关系。
教室类中的addStudent将 Student作为参数,这体现了Class类依赖于Student类。
代码如下:
// 学生
class Student
{
public:Student(const std::string& name) : name(name) {}void introduce() const {std::cout << "Hello, my name is " << name << std::endl;}private:std::string name;
};// 假设有一个表示班级的类,它依赖于 Student类
class Classroom
{
public:Classroom(const std::string& className) : className(className) {}void addStudent(const Student& student) // 接口中 依赖于 Person 类{students.push_back(student);}void listStudents() const {std::cout << "Students in " << className << ":" << std::endl;for (constauto& student : students) {student.introduce();}}private:std::string className;std::vector<Student> students; // 依赖于学生类
};int main() {// 创建一个 Person 对象Student alice("Alice");Student andy("andy");Student anna("anna");// 创建一个 Classroom 对象,并在其中添加 Person 对象Classroom mathClass("Math Class");mathClass.addStudent(alice);mathClass.addStudent(andy);mathClass.addStudent(anna);// 列出班级中的学生mathClass.listStudents();/*Students in Math Class:
Hello, my name is Alice
Hello, my name is andy
Hello, my name is anna*/return 0;
}
在这个例子中,Classroom 类依赖于 Student 类,因为 Classroom 类的成员函数使用了 Student 类的对象。这种依赖关系可以通过成员函数参数、成员变量等方式体现。
UML 表示
**描述:**元素A的变化会影响元素B,那么B和A的关系是依赖关系,B依赖A。要避免双向依赖,一般来说,不应该存在双向依赖。关联、实现、泛化都是依赖关系。上边的教室和学生例子,表示如下:
箭头及指向: 用带箭头的虚线表示,箭头指向被依赖元素。
关联
关联是一种弱所属关系,但并不是从属关系,类之间是平等的。关联可以有方向,可以是单向关联,也可以是双向关联。在C++中,通过在一个类中定义其他类指针类型的成员来实现关联。
例子
class A
{
B *b;
};
class B
{
public:A *a;
};
UML图
描述:关联关系是类与类之间的联结,它使一个类知道另一个类的属性和方法,指明了事物的对象之间的联系,如:老师与学生、丈夫与妻子。关联可以是双向的,也可以是单向的,还有自身关联。
箭头及指向: 用带普通箭头的实心线表示,指向被拥有者。
聚合
聚合也是一种弱所属关系,部分与整体之间没有相同的生命周期,整体消亡后部分可以独立存在,就像汽车和司机的关系。
代码实现时,整体类中传入一个部分类的指针,部分类已经在整体类外被构造,因而在整体类析构的时候,部分类并没有被析构。
例子
下面的例子中,在汽车类中包含4个车轮类,汽车和车轮就是一种聚合关系,汽车类中传入的时车轮类的指针,汽车被销毁之后,车轮并不会被销毁。
class Wheel {
public:Wheel(int diameter) : diameter(diameter) {}~Wheel(){std::cout << "Wheel 对象不会被销毁" << std::endl;}int getDiameter() const {return diameter;}private:int diameter;
};// 假设有一个表示汽车的类,它与车轮类存在聚合关系
class Car {
public:Car(const std::string& model) : model(model) {}~Car(){std::cout << "Car对象被销毁" << std::endl;}// 聚合关系:Car 类包含 Wheel 类的对象(以指针形式)void addWheel(Wheel* wheel) {wheels.push_back(wheel);}void listWheels() const {std::cout << "Wheels of the car model " << model << ":" << std::endl;for (constauto& wheel : wheels){std::cout << "- Diameter: " << wheel->getDiameter() << " inches" << std::endl;}}private:std::string model;std::vector<Wheel*> wheels;
};
int main() {// 创建车轮对象Wheel frontLeftWheel(18);Wheel frontRightWheel(18);Wheel rearLeftWheel(18);Wheel rearRightWheel(18);// 创建汽车对象并在其中添加车轮对象{Car myCar("Sedan");myCar.addWheel(&frontLeftWheel);myCar.addWheel(&frontRightWheel);myCar.addWheel(&rearLeftWheel);myCar.addWheel(&rearRightWheel);// 列出汽车的车轮myCar.listWheels();}// 到这里,只有car对象被销毁,但是Wheel对象没有被销毁。int a = 0;return 0;
}
关联和聚合的区别
关联和聚合的区别主要在语义上,关联的两个对象之间一般是平等的,例如你是我的朋友;聚合则一般不是平等的,例如一个公司包含了很多员工,其实现上是差不多的。
UML 表示
它是整体与部分(整体 has a 部分)的关系,且部分可以离开整体而单独存在,如车和轮胎是整体和部分的关系,轮胎离开车仍然可以存在。聚合关系是关联关系的一种,是强的关联关系,关联和聚合在语法上无法区分,必须考察具体的逻辑关系。
箭头及指向: 用带空心菱形的实线表示,菱形指向整体。
组合关系
类与类之间的组合关系表示一个类对象包含了另一个类对象,它是一种 “contains-a” 的关系,且它们的生命周期是相互关联的。组合关系通常用于表示整体与部分之间的强关系,组合关系的两个对象往往具有相同的生命周期,被组合对象在组合对象创建内部创建,在组合对象销毁之前,组合对象销毁。
例子
Car类与 Engine 类之间存在组合关系。Car 类包含了 Engine 类的对象作为成员变量,而不是使用指针或其他间接方式。这种关系表明一个 Car 对象的生命周期和其中包含的 Engine 对象的生命周期是相互关联的。当 Car 对象被销毁时,其中包含的 Engine 对象也会被销毁。
class Engine
{
public:Engine(const std::string& type) : type(type) {}const std::string& getType() const{return type;}private:std::string type;};class Car
{
public:Car(const string& model, const string& engine):carEngine(engine){this->model = model;}// 显示汽车信息void displayInfo() const {std::cout << "Car Model: " << model << std::endl;std::cout << "Engine Type: " << carEngine.getType() << std::endl;}
private:string model;Engine carEngine;};int main()
{Car car1("Bwn", "hongqi");car1.displayInfo();/*Car Model: BwnEngine Type: hongqi*/return 0;
}
组合与聚合区别:生命周期判断
到底是组合还是聚合,其实现形式(用成员还是指针)不是判断标准,两者生命周期的差异,才是判断标准。两者生命周期相同的,是组合,比如成员变量,你这里用指针的实现,而生命周期不同的,才是聚合。
UML 表示
描述: 组合关系是整体与部分的关系,但部分不能离开整体而单独存在。如公司和部门是整体和部分的关系,没有公司就不存在部门。
用带实心菱形的实线表示,菱形指向整体。
实现/接口
实现/接口 对应的是面向对象中的"接口"。在C++中,接口通过的纯虚函数来实现接口,C++的多态就是通过虚函数来实现的。C++的关键字中并没有interface,但java和C#中有interface关键字,即接口。
对于C++来说,接口相当于抽象类,即其中的成员函数都是纯虚函数,只有声明,没有实现。如下:
class abstractClass{virtualvoid memfunc1() = 0;virtual void memfucn2() = 0;};
abstractClass是一个用于实现接口的纯抽象类,仅包括纯虚函数的类(一般用作基类,派生类进行具体的实现)。纯虚函数是指用=0标记的虚函数。
抽象类是不能实例化的,换句话说,它只是提供一个interface的功能,它并不实现这些纯虚函数。
UML表示
实现关系实际上就是A类实现B接,A类实现了抽象类。
箭头指向:空心三角形箭头+ 虚线,箭头指向接口。
继承
继承时面向对象的三大特征之一,用is-a 表示。
继承表达的是一种上下级的关系(is-a),聚合表达的是一种松散的整体和局部的关系(has-a),而组合表达的是一种紧密的整体局部关系(contain-a)。
UML 表示
继承的UML表示:空心三角形+ 实线,子类指向父类,空心三角指向父类。
可见性:公有(Public)“+”、私有(Private)“-”、受保护(Protected)“#”