UML类图的六大关系:依赖,泛化,实现,关联,聚合,组合
1.依赖关系
依赖关系是 UML 类图中一种相对较弱的关系,用于表明一个类在某种程度上依赖于另一个类来完成特定任务。当类 A 的某个方法使用了类 B 的对象作为参数、局部变量,或是调用了类 B 的静态方法时,就存在类 A 对类 B 的依赖关系。这种关系通常是临时性的,仅在类 A 执行特定操作时才会关联到类 B。
- 特点:
- 临时性:依赖关系不像关联、聚合、组合那样表示类之间长期稳定的结构联系,它只是方法执行期间的短暂关联。
- 单向或双向:可以是单向的,即 A 依赖 B 但 B 不依赖 A;也可能是双向的,不过较为少见,意味着双方的方法互相有所依赖。
- 松散耦合:依赖关系下的两个类耦合度较低,被依赖类的变动对依赖类的影响范围通常局限于使用到的具体方法。
- 使用对象作为参数产生依赖
在这段代码中,ReportWriter
类依赖于Printer
类。ReportWriter
类的generateReport
方法需要借助Printer
类的printText
方法来输出报告内容,Printer
类的对象作为参数传入,一旦generateReport
方法执行完毕,这种依赖联系在本次操作中就结束了。class Printer { public: void printText(const std::string& text) { std::cout << text << std::endl; } }; class ReportWriter { public: void generateReport(Printer& printer) { std::string report = "This is a sample report"; printer.printText(report); } };
- 使用局部变量引发依赖
MathStudent
类的doHomework
方法依赖Calculator
类。在doHomework
方法内部,创建了Calculator
类的局部变量calc
,并使用它的add
方法完成计算,计算完成后,calc
的生命周期结束,此次依赖关系也随之结束。class Calculator { public: int add(int a, int b) { return a + b; } }; class MathStudent { public: void doHomework() { Calculator calc; int result = calc.add(3, 4); std::cout << "The result of calculation is: " << result << std::endl; } };
- 调用静态方法形成依赖
Programmer
类的writeCode
方法依赖Logger
类。通过调用Logger
类的静态方法logMessage
,Programmer
类实现了记录日志的功能,这同样是一种依赖关系,而且由于调用静态方法,甚至不需要创建Logger
类的实例。class Logger { public: static void logMessage(const std::string& msg) { std::cout << "Log: " << msg << std::endl; } }; class Programmer { public: void writeCode() { Logger::logMessage("Writing some code..."); } };
2.泛化关系
泛化关系(Generalization)在 UML 类图里体现的是一种继承关系,它遵循 “is-a” 原则,也就是子类是父类的一种特殊形式。通过泛化,子类能够继承父类的属性和方法,同时还能添加自身独有的属性与方法,实现代码复用与功能扩展。
- 特点:
- 代码复用:父类定义了通用的属性与行为,子类无需重复编写,直接继承即可使用,节省开发精力。例如,若多个子类都需要某个共有的基础方法,在父类中定义一次就行。
- 层次结构:构建起清晰的类层次体系,便于组织和管理复杂的类关系。比如在图形绘制系统中,各种具体图形类从通用的 “图形” 父类派生,逻辑清晰。
- 多态支持:结合虚函数机制,子类能重写父类方法,让程序运行时基于对象实际类型来动态调用合适的方法,增强程序灵活性。
// 定义父类:动物类Animal class Animal { public: std::string name; Animal(const std::string& n) : name(n) {} virtual void makeSound() { std::cout << name << " makes a sound" << std::endl; } }; // 定义子类:狗类Dog,继承自Animal类 class Dog : public Animal { public: Dog(const std::string& n) : Animal(n) {} void makeSound() override { std::cout << name << " barks" << std::endl; } }; // 定义子类:猫类Cat,继承自Animal类 class Cat : public Animal { public: Cat(const std::string& n) : Animal(n) {} void makeSound() override { std::cout << name << " meows" << std::endl; } };
// 图形基类 class Shape { protected: int color; public: Shape(int c) : color(c) {} virtual void draw() = 0; virtual double getArea() = 0; }; // 圆形,继承自 Shape class Circle : public Shape { private: double radius; public: Circle(int c, double r) : Shape(c), radius(r) {} void draw() override { std::cout << "Drawing a circle with color " << color << std::endl; } double getArea() override { return 3.14 * radius * radius; } }; // 矩形,继承自 Shape class Rectangle : public Shape { private: double width, height; public: Rectangle(int c, double w, double h) : Shape(c), width(w), height(h) {} void draw() override { std::cout << "Drawing a rectangle with color " << color << std::endl; } double getArea() override { return width * height; } };
// 交通工具基类 class Vehicle { protected: int speed; int wheels; public: Vehicle(int s, int w) : speed(s), wheels(w) {} virtual void move() = 0; }; // 汽车,继承自 Vehicle class Car : public Vehicle { public: Car(int s, int w) : Vehicle(s, w) {} void move() override { std::cout << "The car is moving at speed " << speed << " with " << wheels << " wheels." << std::endl; } }; // 自行车,继承自 Vehicle class Bicycle : public Vehicle { public: Bicycle(int s, int w) : Vehicle(s, w) {} void move() override { std::cout << "The bicycle is moving at speed " << speed << " with " << wheels << " wheels." << std::endl; } };
// 员工基类 class Employee { protected: std::string name; int salary; public: Employee(const std::string& n, int s) : name(n), salary(s) {} virtual void work() = 0; }; // 程序员,继承自 Employee class Programmer : public Employee { public: Programmer(const std::string& n, int s) : Employee(n, s) {} void work() override { std::cout << name << " is coding." << std::endl; } }; // 销售,继承自 Employee class Salesperson : public Employee { public: Salesperson(const std::string& n, int s) : Employee(n, s) {} void work() override { std::cout << name << " is selling." << std::endl; } };
3.实现关系
在 UML 类图里,实现关系指的是类与接口之间的一种契约关系。接口定义了一组抽象的操作(方法),而实现类负责为这些抽象操作提供具体的实现代码,以此来满足接口所设定的行为规范。这种关系让程序具备更好的灵活性与可扩展性,不同的实现类能够替换使用,只要它们遵循相同的接口标准。
- 特点:
- 抽象性与具体性结合:接口作为抽象的概念,仅勾勒行为轮廓,不涉及具体实现细节;实现类则把这些抽象方法落地,化为真实可执行的代码。
- 多态支持:基于实现关系,程序能利用多态特性,通过接口类型的指针或引用,调用不同实现类的对应方法,增强代码灵活性,使程序可以动态适应变化。
- 解耦与可替换性:依赖接口编程,而非依赖具体类,能降低类与类之间的耦合度。当需要更换功能实现时,只要新的实现类遵循原接口,就能无缝替换,无需大面积改动代码。
// 定义接口类:可绘制接口 Drawable class Drawable { public: virtual void draw() = 0; }; // 定义实现类:圆形类 Circle,实现 Drawable 接口 class Circle : public Drawable { public: void draw() override { std::cout << "Drawing a circle." << std::endl; } }; // 定义实现类:矩形类 Rectangle,实现 Drawable 接口 class Rectangle : public Drawable { public: void draw() override { std::cout << "Drawing a rectangle." << std::endl; } };
// 图形渲染接口 class GraphicRenderer { public: virtual void render() = 0; virtual ~GraphicRenderer() = default; }; // OpenGL 渲染实现类 class OpenGLRenderer : public GraphicRenderer { public: void render() override { std::cout << "Rendering using OpenGL" << std::endl; } }; // Vulkan 渲染实现类 class VulkanRenderer : public GraphicRenderer { public: void render() override { std::cout << "Rendering using Vulkan" << std::endl; } };
// 数据持久化接口 class DataPersistence { public: virtual void saveData(const std::string& data) = 0; virtual std::string loadData() = 0; virtual ~DataPersistence() = default; }; // 文件系统持久化实现类 class FileSystemPersistence : public DataPersistence { public: void saveData(const std::string& data) override { std::ofstream file("data.txt"); if (file.is_open()) { file << data; file.close(); } } std::string loadData() override { std::ifstream file("data.txt"); std::string result; if (file.is_open()) { std::getline(file, result); file.close(); } return result; } }; // 数据库持久化实现类 class DatabasePersistence : public DataPersistence { public: void saveData(const std::string& data) override { // 假设这里有数据库连接和插入数据的代码 std::cout << "Saving data to database: " << data << std::endl; } std::string loadData() override { // 假设这里有数据库查询和获取数据的代码 return "Data from database"; } };
// 排序算法接口 class Sorter { public: virtual void sort(std::vector<int>& data) = 0; virtual ~Sorter() = default; }; // 冒泡排序实现类 class BubbleSorter : public Sorter { public: void sort(std::vector<int>& data) override { int n = data.size(); for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (data[j] > data[j + 1]) { std::swap(data[j], data[j + 1]); } } } } }; // 快速排序实现类 class QuickSorter : public Sorter { public: void sort(std::vector<int>& data) override { // 快速排序的具体实现代码 // 此处省略完整代码 std::cout << "Sorting data with Quick Sort" << std::endl; } };
4.关联关系
在 UML 类图里,关联关系用于表示类与类之间存在某种语义上的连接,这种连接通常意味着它们之间有稳定且长期的交互。关联关系可以是单向的,也可以是双向的,并且常带有多重性(Multiplicity)标识,用来表明一个类的对象与另一个类的对象之间数量上的对应关系。与依赖关系相比,关联关系更为紧密,它不是临时性的方法调用,而是类结构层面的一种联系。
- 特点:
- 稳定性:关联关系描述的是类之间相对持久的关系,不像依赖关系那样只是在方法执行期间短暂出现。例如,在电商系统里,顾客和订单之间的关系就是长期稳定的,顾客长期会下订单,订单长期关联特定顾客。
- 方向性:可以是单向关联,即一个类知晓另一个类,但反之不然;也能是双向关联,意味着双方类都清楚彼此的存在,能够互相访问。
- 多重性:多重性用数字、范围或者特定符号(比如 “*” 表示多个 )在关联线上标注,精准定义两个类对象间数量对应,像一个班级有多个学生,一个学生只属于一个班级。
- 单向关联
在这段代码中,Employee
类与Company
类是单向关联关系,Employee
类持有指向Company
类的指针,员工知晓自己所属的公司,能调用公司相关方法(如获取公司名 ),但公司类并没有反向关联员工类的设计。class Company; // 员工类 class Employee { private: Company* employer; public: Employee(Company* c) : employer(c) {} void displayCompany() { if (employer!= nullptr) { std::cout << "Works for: " << employer->getName() << std::endl; } } }; // 公司类 class Company { private: std::string name; public: Company(const std::string& n) : name(n) {} std::string getName() { return name; } };
- 双向关联
这里,Student
类和Teacher
类呈现双向关联。学生类持有教师类的指针向量,方便添加、罗列自己的教师;教师类也持有学生类的指针向量,能添加学生,双方类都对彼此存在关联,体现了较为紧密的长期互动关系。class Teacher; // 学生类 class Student { private: std::vector<Teacher*> teachers; public: void addTeacher(Teacher* t) { teachers.push_back(t); } void listTeachers() { for (auto teacher : teachers) { std::cout << "Has teacher: " << teacher->getName() << std::endl; } } }; // 教师类 class Teacher { private: std::vector<Student*> students; public: void addStudent(Student* s) { students.push_back(s); } std::string getName() { return "Teacher Name"; } };
- 带多重性的关联
在 UML 类图里,Team
类与Player
类的关联线旁可标注 “1..*”,意味着一个球队有 1 个到多个球员 ,在代码里用std::vector<Player*>
实现了这种一对多的数量对应关系,表明了类之间基于数量的关联特性。// 球队类 class Team { private: std::vector<Player*> players; public: void addPlayer(Player* p) { players.push_back(p); } }; // 球员类 class Player { };
5.聚合关系
聚合关系是 UML 类图里表示整体与部分关系的一种,它属于特殊的关联关系。在聚合关系中,部分能够独立于整体存在,整体和部分有着各自的生命周期。整体对象包含对部分对象的引用,不过,部分对象的所有权不完全归属于整体对象,也就是说,即使整体被销毁了,部分依然可以存活,并且能被其他整体对象复用。
- 特点:
- 弱拥有关系:整体 “拥有” 部分,但这种拥有比较松散,部分并不紧密依附于特定的整体。例如,一台电脑有一个硬盘,硬盘坏了可以拆下来装到别的电脑上,硬盘有独立于这台电脑的生存能力。
- 独立生命周期:部分对象在整体对象创建之前可以已经存在,在整体对象销毁之后也能继续留存,二者生命周期没有强绑定。
- 可替换性:由于部分的独立性,在需要的时候,很容易将某个部分从一个整体中拆卸下来,替换成其他同类型的部分。
// 定义部分类:发动机类 Engine class Engine { public: void start() { std::cout << "Engine started." << std::endl; } }; // 定义整体类:汽车类 Car class Car { private: Engine* engine; public: Car(Engine* e) : engine(e) {} ~Car() { // 汽车销毁时,不负责销毁发动机 // 因为发动机有自己独立的生命周期 } void drive() { if (engine!= nullptr) { engine->start(); std::cout << "Car is driving." << std::endl; } } };
// 定义球员类 class Player { private: std::string name; public: Player(const std::string& n) : name(n) {} void play() { std::cout << name << " is playing." << std::endl; } }; // 定义球队类 class Team { private: std::vector<Player*> players; public: void addPlayer(Player* p) { players.push_back(p); } ~Team() { // 球队解散时,球员不会随之消失 // 他们可以转会到其他球队 for (auto player : players) { delete player; } players.clear(); } void playGame() { for (auto player : players) { player->play(); } std::cout << "The team is playing a game." << std::endl; } };
// 定义书籍类 class Book { private: std::string title; public: Book(const std::string& t) : title(t) {} std::string getTitle() { return title; } }; // 定义图书馆类 class Library { private: std::vector<Book*> books; public: void addBook(Book* b) { books.push_back(b); } ~Library() { // 图书馆关闭时,书籍不会被销毁 // 它们可以被转移到其他图书馆 for (auto book : books) { delete book; } books.clear(); } void listBooks() { for (auto book : books) { std::cout << book->getTitle() << std::endl; } } };
6.组合关系
组合关系同样用于表示整体与部分的关系,但相较于聚合关系,它是一种更强的 “拥有” 形式。在组合关系里,部分不能脱离整体而独立存在,整体对象与部分对象的生命周期紧密绑定,当整体被销毁时,部分对象必然会随之销毁。组合关系体现了一种强内聚性,强调部分是整体不可或缺的一部分。
- 特点:
- 强依赖:部分对象完全依赖于整体对象,离开整体,部分就失去了存在的意义。例如,在人体中,心脏是人体的一部分,脱离了人体,心脏就无法维持正常功能。
- 同步生命周期:整体对象创建时,部分对象通常也随之创建;整体对象销毁时,部分对象会被自动销毁,它们的生命周期完全同步。
- 紧密耦合:整体与部分之间耦合紧密,这意味着对整体或部分的修改,可能会较大程度影响另一方,不过也保证了数据的一致性和完整性。
// 定义部分类:心脏类 Heart class Heart { public: void beat() { std::cout << "Heart is beating." << std::endl; } }; // 定义整体类:人类 Human class Human { private: Heart heart; public: Human() {} ~Human() { // 当人类对象被销毁时,心脏对象也自动销毁 // 无需额外处理 } void live() { heart.beat(); std::cout << "Human is living." << std::endl; } };
// 按钮类 class Button { public: void click() { std::cout << "Button is clicked." << std::endl; } }; // 窗口类 class Window { private: std::vector<Button> buttons; public: Window() { // 初始化时创建按钮 Button closeButton; Button minimizeButton; buttons.push_back(closeButton); buttons.push_back(minimizeButton); } void handleEvents() { for (auto& button : buttons) { button.click(); } } ~Window() { // 当窗口被销毁时,按钮也随之销毁 // 因为按钮是窗口的一部分,不能独立存在于这个窗口系统之外 } };
// 书页类 class Page { private: std::string content; public: Page(const std::string& c) : content(c) {} std::string getContent() { return content; } }; // 书类 class Book { private: std::vector<Page> pages; public: Book() { // 初始化书页 Page p1("This is the content of page 1."); Page p2("This is the content of page 2."); pages.push_back(p1); pages.push_back(p2); } void displayPages() { for (auto& page : pages) { std::cout << page.getContent() << std::endl; } } ~Book() { // 当书被销毁时,书页也随之销毁 // 书页不能脱离书而独立存在 } };
// 发动机类 class Engine { public: void start() { std::cout << "Engine is starting." << std::endl; } }; // 汽车类 class Car { private: Engine engine; public: Car() {} void drive() { engine.start(); std::cout << "The car is driving." << std::endl; } ~Car() { // 当汽车被销毁时,发动机也自动销毁 // 因为发动机是汽车的一部分,不能独立存在 } };