C++ 面向对象核心概念全解 | Kai@Codehubble 技术笔记

引言

在面向对象编程体系中,类与类之间的关系构建起程序架构的骨架。本文将以 C++ 语言为实践载体,采用 plantUML 绘制 UML 类图,通过将 plantUML 代码与其渲染图对照展示,结合可视化结果,深度剖析继承(is-a)、聚合 / 组合(has-a)、依赖(dependence)、关联等核心关系模型。同时穿插接口实现机制、重载与重写的多态特性解析,通过典型代码案例与 UML 图示对照,帮助开发者精准把握不同关系的设计边界与应用场景。

下文中,会通过plantuml原始语句->UML渲染图-->类的实现的顺序逐一描述对应关系。

基础结构描述

类结构表述

类是面向对象编程的基本单元,封装了数据和操作数据的方法。在 UML 中,类用矩形表示,分为三个部分:类名、属性、方法。在 C++ 中,通过class关键字定义类,包含成员变量(属性)和成员函数(方法)。

@startuml

class Person {
  - id: int
  - name: string
  + Person(id: int, name: string)
  + getName(): string
  + setName(name: string): void
  + displayInfo(): void
}

@enduml

image-20250730231404467

class Person {
private:  // 私有属性,UML中用"-"表示
    int id;
    string name;
public:  // 公共方法,UML中用"+"表示
    // 构造函数
    Person(int i, string n) : id(i), name(n) {}
    // 获取姓名
    string getName() {
        return name;
    }
    // 设置姓名
    void setName(string n) {
        name = n;
    }
    // 显示信息
    void displayInfo() {
        cout << "ID: " << id << ", Name: " << name << endl;
    }
};

说明:UML 类图中,-表示私有成员,+表示公共成员,#表示保护成员;C++ 中通过private、public和protect关键字控制访问权限,与 UML 的符号一一对应。

静态成员表示

静态成员属于类本身而非实例,在 UML 中用{static}标识,在 C++ 中通过static关键字定义。

@startuml
class Counter {
  - count: int {static}
  + increment(): void {static}
  + getCount(): int {static}
}
@enduml

image-20250730233432521

注:在渲染图中会表示为下划线

class Counter {
private:
    static int count;  // 静态成员变量
public:
    static void increment() {  // 静态成员函数
        count++;
    }
    static int getCount() {
        return count;
    }
};

int Counter::count = 0;  // 静态成员初始化

// 使用示例
int main() {
    Counter::increment();
    Counter::increment();
    cout << "Count: " << Counter::getCount() << endl;  // 输出2
    return 0;
}

接口(C++纯虚类实现)

接口是一种特殊的抽象,只包含方法声明而没有实现,用于定义类应遵循的行为规范。在 UML 中,接口用带有<<interface>> stereotype 的矩形表示,或简化为圆圈加接口名。在 C++ 中,通过纯虚函数类模拟接口。

@startuml
interface Payment {
  <<interface>>
  + processAmount(amount: double): bool
  + refundAmount(amount: double): bool
}
@enduml

image-20250730232319223

注:这里的<<interface>>可以省略,这里只是为了强调说明

// C++中用纯虚函数类表示接口
class Payment {
public:
    // 纯虚函数,无实现
    virtual bool processAmount(double amount) = 0;
    virtual bool refundAmount(double amount) = 0;
    // 虚析构函数,确保子类析构正确
    virtual ~Payment() {}
};

类成员的行为特性(多态体现)

方法重载 (Overload)-->编译时多态

重载(Overload)是指在同一个类中,多个方法具有相同的名称但参数列表不同(参数类型、个数或顺序不同)。在 UML 中,通过在同一方法名下列出不同参数列表的方法表示;在 C++ 中,直接定义同名不同参数的函数即可。

@startuml
class Calculator {
  + add(a: int, b: int): int
  + add(a: double, b: double): double
  + add(a: int, b: int, c: int): int
}
@enduml

image-20250730232830165

class Calculator {
public:
    // 两个int相加
    int add(int a, int b) {
        return a + b;
    }
    // 两个double相加(参数类型不同)
    double add(double a, double b) {
        return a + b;
    }
    // 三个int相加(参数个数不同)
    int add(int a, int b, int c) {
        return a + b + c;
    }
};

方法重写(Override)-->运行时多态(虚函数支持)

多态是面向对象的核心特性之一,通过父类指针或引用调用子类重写的虚函数,实现 “一个接口,多种实现”。在 UML 中,用{virtual}标识虚函数,子类重写的函数保持名称和参数列表一致;在 C++ 中,通过virtual关键字声明虚函数,子类用override关键字显式重写。

@startuml
class Animal {
  + makeSound(): void {virtual}
}

class Dog {
  + makeSound(): void {override}
}

class Cat {
  + makeSound(): void {override}
}

Animal <|-- Dog
Animal <|-- Cat
@enduml

image-20250730233055742

#include <iostream>
using namespace std;

// 父类:动物
class Animal {
public:
    // 虚函数:声明可被重写
    virtual void makeSound() {
        cout << "Animal makes a sound" << endl;
    }
    virtual ~Animal() {}  // 虚析构函数,确保子类析构正确
};

// 子类:狗
class Dog : public Animal {
public:
    // 重写父类虚函数,用override显式标识
    void makeSound() override {
        cout << "Dog barks: Woof! Woof!" << endl;
    }
};

// 子类:猫
class Cat : public Animal {
public:
    // 重写父类虚函数
    void makeSound() override {
        cout << "Cat meows: Meow! Meow!" << endl;
    }
};

// 多态演示:通过父类指针调用子类方法
void playSound(Animal* animal) {
    animal->makeSound();  // 实际调用的是子类重写的方法
}

int main() {
    Animal* animal1 = new Dog();
    Animal* animal2 = new Cat();

    playSound(animal1);  // 输出"Dog barks: Woof! Woof!"
    playSound(animal2);  // 输出"Cat meows: Meow! Meow!"

    delete animal1;
    delete animal2;
    return 0;
}

核心特征

  1. 父类声明虚函数(virtual),子类用override重写,函数签名(名称、参数、返回值)必须完全一致。
  2. 通过父类指针或引用调用时,编译器会根据对象实际类型动态绑定到对应的子类方法。
  3. 多态实现了行为的动态扩展,新增子类时无需修改调用代码,符合 “开闭原则”。

类的设计特性(面向对象核心)

封装(Encapsulation)

访问控制、信息隐藏-->支撑继承/关联的安全设计

封装是将数据和操作数据的方法捆绑在一起,并通过访问权限控制对外暴露的接口。在 UML 中通过权限修饰符(-私有、#保护、+公共)表示,在 C++ 中通过private、protected、public关键字实现。

通过信息隐藏,为继承、关联打基础。

@startuml
class BankAccount {
  - accountNumber: string
  - balance: double
  # owner: Person
  - logTransaction(desc: string): void
  # verifyBalance(amount: double): bool
  + BankAccount(num: string, owner: Person)
  + deposit(amount: double): void
  + withdraw(amount: double): bool
  + getBalance(): double
}
@enduml

image-20250730230520905

class Person { /* 省略实现 */ };

class BankAccount {
private:  // 完全封装,仅类内部可访问
    string accountNumber;
    double balance;

    // 私有成员函数:仅类内部使用,记录交易日志
    void logTransaction(string desc) {
        cout << "Transaction logged: " << desc << endl;
    }

protected:  // 封装,类及子类可访问
    Person owner;

    // 保护成员函数:类及子类可使用,验证余额是否充足
    bool verifyBalance(double amount) {
        return amount <= balance;
    }

public:  // 对外开放的接口
    BankAccount(string num, Person o) : accountNumber(num), owner(o), balance(0) {}

    // 公共成员函数:外部可调用,存款操作
    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            logTransaction("Deposit: " + to_string(amount));
        }
    }

    // 公共成员函数:外部可调用,取款操作
    bool withdraw(double amount) {
        if (amount > 0 && verifyBalance(amount)) {
            balance -= amount;
            logTransaction("Withdrawal: " + to_string(amount));
            return true;
        }
        return false;
    }

    // 公共成员函数:外部可调用,获取余额
    double getBalance() {
        return balance;
    }
};

核心价值:通过对成员函数区分访问权限,进一步细化了接口的开放性。私有函数logTransaction确保交易记录方式不被外部干扰,保护函数verifyBalance允许子类复用余额验证逻辑,公共函数则提供安全的外部交互接口,全方位提升代码的安全性和可维护性。


抽象(Abstraction)

抽象类、纯虚函数-->为多态(重写)提供抽象契约

抽象是提取事物的本质特征而忽略非本质细节。在 UML 中用抽象类(类名斜体)表示,在 C++ 中通过包含纯虚函数的类实现。

通过纯虚函数实现多态。

@startuml
abstract class Shape {
  + calculateArea(): double {abstract}
  + calculatePerimeter(): double {abstract}
}

class Circle {
  + radius: double
  + calculateArea(): double
  + calculatePerimeter(): double
}

'继承关系:Shape 是父类,Circle 是子类
Shape <|-- Circle

@enduml

image-20250730231106523

// 抽象类:仅定义接口,不提供实现
class Shape {
public:
    // 纯虚函数体现抽象性
    virtual double calculateArea() = 0;
    virtual double calculatePerimeter() = 0;
    virtual ~Shape() {}
};

// 具体实现类
class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    double calculateArea() override {
        return 3.1415 * radius * radius;
    }
    double calculatePerimeter() override {
        return 2 * 3.1415 * radius;
    }
};

类之间的关系

继承关系(is-a)-->类的层级复用

is-a 关系体现的是类之间的继承层次,子类是父类的具体实现,完全拥有父类的特性并可扩展自身功能。在 UML 中用带空心三角的实线表示(<|--),在 C++ 中通过class 子类 : 继承方式 父类语法实现。

@startuml
class Vehicle {
  + speed: int
  + move(): void
}

class Car {
  + numDoors: int
  + honk(): void
}

class Bicycle {
  + hasBasket: bool
  + ringBell(): void
}

Vehicle <|-- Car
Vehicle <|-- Bicycle
@enduml

image-20250730234106006

// 父类:交通工具
class Vehicle {
protected:
    int speed;
public:
    Vehicle(int s) : speed(s) {}
    void move() {
        cout << "Moving at " << speed << " km/h" << endl;
    }
};

// 子类:汽车(是一种交通工具)
class Car : public Vehicle {
private:
    int numDoors;
public:
    Car(int s, int doors) : Vehicle(s), numDoors(doors) {}
    void honk() {
        cout << "Car honking: Beep beep!" << endl;
    }
};

// 子类:自行车(是一种交通工具)
class Bicycle : public Vehicle {
private:
    bool hasBasket;
public:
    Bicycle(int s, bool basket) : Vehicle(s), hasBasket(basket) {}
    void ringBell() {
        cout << "Bicycle ringing: Ding ding!" << endl;
    }
};

核心特征:子类必然是父类的一种,如汽车和自行车一定属于交通工具,继承关系具有不可分割的必然性。

包含关系(has-a)

has-a 关系表示类之间的包含关系,在 UML 中用带菱形的实线表示(聚合o-/ 组合*-)。组合是强包含关系(部分不能脱离整体存在),聚合是弱包含关系(部分可独立存在)。在 C++ 中通过成员变量实现。

@startuml

class Bird {
  + name: string
  + fly(): void
}

class Wing {
  + flutter(): void
}

class BirdFlock {
  + flockName: string
  + addBird(bird: Bird): void
  + getBirds(): Bird[]
}

Bird *- Wing: has (组合)
BirdFlock o- Bird: contains (聚合)

@enduml

image-20250730234503643

图中关系说明

  • 鸟(Bird)与翅膀(Wing)之间的*-符号表示组合,翅膀是鸟的不可分割部分;

  • 鸟群(BirdFlock)与鸟(Bird)之间的o-符号表示聚合,鸟可以脱离鸟群独立存在。

组合(Composition)-->强耦合,部分与整体共生

翅膀作为鸟的核心组成部分,无法脱离鸟单独存在,因此在代码中通过成员变量直接定义(而非指针或引用),确保鸟被销毁时翅膀也随之销毁。

// 鸟翅膀(部分)
class Wing {
public:
    // 翅膀的颤动动作
    void flutter() {
        cout << "Wing fluttering" << endl;
    }
};

// 鸟(整体)
class Bird {
private:
    string name;
    // 直接包含翅膀对象(组合关系)
    Wing leftWing;  
    Wing rightWing;
public:
    Bird(string n) : name(n) {}

    // 鸟飞行时依赖翅膀的动作
    void fly() {
        leftWing.flutter();
        rightWing.flutter();
        cout << name << " is flying freely" << endl;
    }

    string getName() {
        return name;
    }
};

组合关系的代码特征

  • 整体类(Bird)中直接定义部分类(Wing)的对象,而非指针;

  • 部分的生命周期由整体管理,无需手动释放内存;

  • 部分无法被多个整体共享(如一只翅膀不能同时属于多只鸟)。

聚合(Aggregation)-->松耦合,部分可独立

鸟群是鸟的集合,鸟可以加入或离开鸟群,因此在代码中通过指针或引用管理鸟的实例,允许鸟在鸟群之外独立存在。

#include <vector>
#include <iostream>
using namespace std;

// 鸟群(整体)
class BirdFlock {
private:
    string flockName;
    // 用指针存储鸟的实例(聚合关系)
    vector<Bird*> birds;  
public:
    BirdFlock(string name) : flockName(name) {}

    // 添加鸟到鸟群
    void addBird(Bird* bird) {
        if (bird != nullptr) {
            birds.push_back(bird);
            cout << bird->getName() << " joined " << flockName << endl;
        }
    }

    // 移除鸟群中的鸟
    void removeBird(Bird* bird) {
        for (auto it = birds.begin(); it != birds.end(); ++it) {
            if (*it == bird) {
                cout << bird->getName() << " left " << flockName << endl;
                birds.erase(it);
                return;
            }
        }
    }

    // 鸟群一起飞行
    void flyTogether() {
        cout << flockName << " is flying together:" << endl;
        for (Bird* bird : birds) {
            bird->fly();
        }
    }

    // 析构函数:不销毁鸟的实例(聚合特性)
    ~BirdFlock() {
        cout << flockName << " disbanded" << endl;
        // 仅清空容器,不删除鸟对象
        birds.clear();  
    }
};

聚合关系的代码特征

  • 整体类(BirdFlock)中通过指针 / 引用管理部分类(Bird)的实例;

  • 部分的生命周期独立于整体,整体销毁时不影响部分;

  • 部分可以被多个整体共享(如一只鸟可暂时加入多个鸟群)。

关系演示

int main() {
    // 创建鸟实例(可独立存在)
    Bird* sparrow = new Bird("Sparrow");
    Bird* pigeon = new Bird("Pigeon");

    // 创建鸟群(聚合关系)
    BirdFlock forestFlock("Forest Flock");
    forestFlock.addBird(sparrow);
    forestFlock.addBird(pigeon);
    forestFlock.flyTogether();

    // 鸟离开鸟群后仍可独立活动
    forestFlock.removeBird(sparrow);
    sparrow->fly();  // 麻雀离开鸟群后仍能飞行

    // 销毁鸟对象(手动管理生命周期)
    delete sparrow;
    delete pigeon;
    return 0;
}

输出结果:

Sparrow joined Forest Flock
Pigeon joined Forest Flock
Forest Flock is flying together:
Wing fluttering
Wing fluttering
Sparrow is flying freely
Wing fluttering
Wing fluttering
Pigeon is flying freely
Sparrow left Forest Flock
Wing fluttering
Wing fluttering
Sparrow is flying freely
Forest Flock disbanded

聚合与组合的核心区别

特性 组合(Composition) 聚合(Aggregation)
依赖强度 强依赖(部分依赖整体存在) 弱依赖(部分可独立存在)
生命周期关联 整体销毁时,部分必被销毁 整体销毁时,部分可继续存在
代码实现方式 整体类中定义部分类的对象 整体类中存储部分类的指针 / 引用
共享性 部分不能被多个整体共享 部分可被多个整体共享
典型实例 鸟与翅膀、人与心脏 鸟群与鸟、公司与员工

应用场景总结

  • 组合关系适用于 “部分是整体不可分割的一部分” 的场景,如:

  • 电脑与 CPU(CPU 无法脱离电脑独立工作);

  • 书籍与章节(章节不能脱离书籍存在)。

  • 聚合关系适用于 “部分可独立于整体存在” 的场景,如:

  • 班级与学生(学生可转学离开班级);

  • 图书馆与书籍(书籍可被借出图书馆)。

正确区分并使用聚合与组合,能更精准地映射现实世界的关联模式,提升代码的逻辑性和可维护性。在设计时需重点关注 “部分是否能脱离整体存在” 这一核心判断标准。

关联关系(usage)

usage 关系(关联)是类之间一种较为松散但长期存在的引用关系,它表明一个类会使用另一个类的功能或属性来完成自身的职责。在 UML 类图中,关联关系通常用实线箭头来表示(-->),箭头指向被使用的类。

在 C++ 中,关联关系一般是通过一个类在其成员变量中持有另一个类的引用或指针来实现,这体现了两个类之间持续的合作关系。根据类之间互动的方向和数量,关联关系可分为单向关联、双向关联和多对多关联等。

单向关联

单向关联是指两个类之间的关联仅在一个方向上存在,即一个类知道并使用另一个类,而被使用的类并不知晓使用它的类。

@startuml

class Customer {
  + name: string
  + getName(): string
}

class Order {
  + orderId: int
  + getCustomer(): Customer
  + displayOrderInfo(): void
}

Order --> Customer: belongs to

@enduml

image-20250730235920021

图中关系说明:订单(Order)与客户(Customer)之间存在单向关联,箭头从 Order 指向 Customer,表示订单知道它所属的客户,而客户并不知晓有该订单的存在。

#include <iostream>
#include <string>
using namespace std;

class Customer {
private:
    string name;
public:
    Customer(string n) : name(n) {}
    string getName() { return name; }
};

class Order {
private:
    int orderId;
    Customer* customer;  // 订单持有客户的指针,体现单向关联
public:
    Order(int id, Customer* c) : orderId(id), customer(c) {}
    Customer* getCustomer() { return customer; }
    void displayOrderInfo() {
        cout << "Order ID: " << orderId << ", belongs to " << customer->getName() << endl;
    }
};

// 演示单向关联的使用
int main() {
    Customer* customer = new Customer("John Doe");
    Order* order = new Order(1001, customer);

    order->displayOrderInfo();  // 订单可以使用客户的信息

    delete order;
    delete customer;
    return 0;
}

输出结果:

Order ID: 1001, belongs to John Doe

单向关联的代码特征

  • 只有一个类(Order)持有另一个类(Customer)的引用或指针;

  • 被引用的类(Customer)无需知晓引用它的类(Order)的存在;

  • 这种关联关系是单向的,只能从持有引用的类向被引用的类进行访问和交互。

双向关联

双向关联是指两个类之间可以相互知晓并使用对方,即每个类都持有对方的引用或指针,能够在两个方向上进行交互。

@startuml

class Student {
  + name: string
  + getCourse(): Course
  + displayStudentInfo(): void
}

class Course {
  + courseName: string
  + getStudent(): Student
  + displayCourseInfo(): void
}

Student <--> Course: enrolls in

@enduml

image-20250731000345532

图中关系说明:学生(Student)与课程(Course)之间存在双向关联,双向箭头表示学生知道自己所选修的课程,课程也知道有哪些学生选修了它。

#include <iostream>
#include <string>
using namespace std;

// 前向声明,解决循环依赖问题
class Course;

class Student {
private:
    string name;
    Course* course;  // 学生持有课程的指针
public:
    Student(string n) : name(n), course(nullptr) {}
    void setCourse(Course* c);
    Course* getCourse() { return course; }
    string getName() { return name; }
    void displayStudentInfo();
};

class Course {
private:
    string courseName;
    Student* student;  // 课程持有学生的指针
public:
    Course(string name) : courseName(name), student(nullptr) {}
    void setStudent(Student* s) { student = s; }
    Student* getStudent() { return student; }
    string getCourseName() { return courseName; }
    void displayCourseInfo() {
        if (student) {
            cout << "Course: " << courseName << ", enrolled student: " << student->getName() << endl;
        } else {
            cout << "Course: " << courseName << ", no enrolled student" << endl;
        }
    }
};

// 类外实现Student的方法,避免循环依赖导致的问题
void Student::setCourse(Course* c) {
    course = c;
    if (c) {
        c->setStudent(this);
    }
}

void Student::displayStudentInfo() {
    if (course) {
        cout << "Student: " << name << ", enrolled in course: " << course->getCourseName() << endl;
    } else {
        cout << "Student: " << name << ", no enrolled course" << endl;
    }
}

// 演示双向关联的使用
int main() {
    Student* student = new Student("Alice");
    Course* course = new Course("Mathematics");

    student->setCourse(course);  // 建立双向关联

    student->displayStudentInfo();
    course->displayCourseInfo();

    delete student;
    delete course;
    return 0;
}

输出结果:

Student: Alice, enrolled in course: Mathematics
Course: Mathematics, enrolled student: Alice

双向关联的代码特征

  • 两个类(Student 和 Course)相互持有对方的引用或指针;

  • 任意一个类都可以通过所持有的引用或指针访问和使用另一个类的功能;

  • 需要注意处理循环依赖问题,通常通过前向声明和类外实现方法来解决。

多对多关联

多对多关联表示多个 A 类对象可以与多个 B 类对象相关联,反之亦然。在实际实现中,通常需要引入一个中间类来管理这种复杂的关联关系。

@startuml

class Book {
  + bookName: string
  + getBookName(): string
}

class Reader {
  + readerName: string
  + getReaderName(): string
}

class BorrowRecord {
  + book: Book
  + reader: Reader
  + borrowDate: string
  + returnDate: string
  + displayRecord(): void
}

Book <-- BorrowRecord
Reader <-- BorrowRecord

@enduml

image-20250731000620481

图中关系说明:书籍(Book)与读者(Reader)之间存在多对多关联,通过借阅记录(BorrowRecord)这个中间类来实现。一个读者可以借阅多本书,一本书也可以被多个读者借阅,借阅记录则记录了具体的借阅信息。

#include <iostream>
#include <string>
#include <vector>
using namespace std;

class Book {
private:
    string bookName;
public:
    Book(string name) : bookName(name) {}
    string getBookName() { return bookName; }
};

class Reader {
private:
    string readerName;
public:
    Reader(string name) : readerName(name) {}
    string getReaderName() { return readerName; }
};

class BorrowRecord {
private:
    Book* book;
    Reader* reader;
    string borrowDate;
    string returnDate;
public:
    BorrowRecord(Book* b, Reader* r, string bDate, string rDate) 
        : book(b), reader(r), borrowDate(bDate), returnDate(rDate) {}
    void displayRecord() {
        cout << reader->getReaderName() << " borrowed " << book->getBookName() 
             << " on " << borrowDate << ", to return on " << returnDate << endl;
    }
};

// 演示多对多关联的使用
int main() {
    // 创建书籍对象
    Book* book1 = new Book("C++ Programming");
    Book* book2 = new Book("Data Structure");

    // 创建读者对象
    Reader* reader1 = new Reader("Bob");
    Reader* reader2 = new Reader("Charlie");

    // 创建借阅记录(中间类实现多对多关联)
    vector<BorrowRecord*> records;
    records.push_back(new BorrowRecord(book1, reader1, "2025-07-01", "2025-07-15"));
    records.push_back(new BorrowRecord(book1, reader2, "2025-07-05", "2025-07-19"));
    records.push_back(new BorrowRecord(book2, reader1, "2025-07-03", "2025-07-17"));

    // 显示借阅记录
    for (BorrowRecord* record : records) {
        record->displayRecord();
    }

    // 释放资源
    for (BorrowRecord* record : records) {
        delete record;
    }
    delete book1;
    delete book2;
    delete reader1;
    delete reader2;

    return 0;
}

多对多关联的代码特征

  • 引入中间类(BorrowRecord)来管理两个类(Book 和 Reader)之间的多对多关系;

  • 中间类持有两个关联类的引用或指针,并可以存储关联的额外信息(如借阅日期、归还日期);

  • 多个 A 类对象可以与多个 B 类对象相关联,通过中间类可以清晰地记录和管理这些关联关系。

关联关系的应用场景总结

  • 单向关联适用于只需在一个方向上进行交互的场景,如订单与客户的关系,订单需要知道所属客户,但客户无需知道具体订单。

  • 双向关联适用于两个类之间需要相互交互的场景,如学生与课程的关系,学生需要知道自己选修的课程,课程也需要知道有哪些学生选修。

  • 多对多关联适用于多个对象之间存在复杂交互的场景,如书籍与读者的关系,通过中间类可以有效管理这种多对多的关联及相关信息。

正确理解和运用关联关系,能够准确地建模现实世界中类之间的合作关系,使代码结构更加清晰、合理,提高软件的可维护性和扩展性。在设计时,需根据类之间的实际互动情况选择合适的关联类型。

依赖关系(dependency)

dependency 依赖关系是指一个类(依赖类)在执行某些操作时,需要另一个类(被依赖类)提供支持,这种依赖通常体现在方法参数、局部变量或返回值中。它是一种临时的、动态的关联,当依赖类的方法执行完毕,这种依赖关系便会暂时消失。

在 UML 类图中,依赖关系用带箭头的虚线表示(---->),箭头从依赖类指向被依赖类,清晰地展现出类之间的依赖方向。

与关联关系相比,依赖关系具有以下显著区别:

  • 关联关系是长期存在的,通常通过成员变量持有引用或指针;而依赖关系是临时的,仅在方法执行过程中存在。

  • 关联关系中,类之间的耦合度相对较高;依赖关系中,类之间的耦合度较低,因为它不涉及长期的引用持有。

依赖关系在代码中主要有多种表现形式,每种形式都体现了依赖类对被依赖类的临时使用。

方法参数中的依赖

当一个类的方法将另一个类的对象作为参数时,就形成了方法参数层面的依赖关系。在方法执行期间,依赖类会使用参数所指向的被依赖类对象来完成特定操作。

@startuml

class Printer {
  + printDocument(doc: Document): void
}

class Document {
  + content: string
  + getContent(): string
}

Printer ----> Document: uses in printDocument

@enduml

image-20250731001340146

图中关系说明:打印机(Printer)类的printDocument方法以文档(Document)类的对象作为参数,表明打印机在打印文档时依赖于文档对象,箭头从 Printer 指向 Document,体现了这种依赖方向。

#include <iostream>
#include <string>
using namespace std;

class Document {
private:
    string content;
public:
    Document(string c) : content(c) {}
    string getContent() { return content; }
};

class Printer {
public:
    // 方法参数为Document对象,形成依赖关系
    void printDocument(Document doc) {
        cout << "Printing document: " << doc.getContent() << endl;
    }
};

// 演示方法参数中的依赖关系
int main() {
    Document doc("Hello, World!");
    Printer printer;

    printer.printDocument(doc);  // 调用方法时形成依赖

    return 0;
}

输出结果:

Printing document: Hello, World!

方法参数依赖的代码特征

  • 依赖类(Printer)的方法参数明确声明为被依赖类(Document)的对象;

  • 仅在方法调用和执行期间存在依赖关系,方法执行结束后,依赖关系解除;

  • 依赖类不持有被依赖类的长期引用,仅在需要时通过参数传递获取。

局部变量中的依赖

当一个类的方法内部创建另一个类的对象作为局部变量,并使用该对象完成操作时,便形成了局部变量层面的依赖关系。这种依赖仅存在于方法执行的局部作用域内。

@startuml

class Logger {
  + log(message: string): void
}

class FileProcessor {
  + processFile(filename: string): void
}

FileProcessor ----> Logger: uses as local variable

@enduml

image-20250731002127129

图中关系说明:文件处理器(FileProcessor)类的processFile方法在执行过程中,会创建日志记录器(Logger)类的对象作为局部变量来记录操作日志,表明文件处理器在处理文件时依赖于日志记录器,箭头从 FileProcessor 指向 Logger。

#include <iostream>
#include <string>
using namespace std;

class Logger {
public:
    void log(string message) {
        cout << "Log: " << message << endl;
    }
};

class FileProcessor {
public:
    void processFile(string filename) {
        // 方法内部创建Logger对象作为局部变量,形成依赖
        Logger logger;
        logger.log("Processing file: " + filename);
        // 执行文件处理操作...
        logger.log("File processing completed: " + filename);
    }
};

// 演示局部变量中的依赖关系
int main() {
    FileProcessor processor;
    processor.processFile("data.txt");

    return 0;
}

输出结果:

Log: Processing file: data.txt​
Log: File processing completed: data.txt

局部变量依赖的代码特征

  • 被依赖类(Logger)的对象在依赖类(FileProcessor)的方法内部创建,仅在该方法的局部作用域内有效;

  • 依赖关系随着方法的开始而建立,随着方法的结束而消失;

  • 完全由依赖类自主控制被依赖类对象的创建和使用,被依赖类无需知晓依赖类的存在。

返回值中的依赖

当一个类的方法返回另一个类的对象时,就形成了返回值层面的依赖关系。这种依赖表明方法的调用者可以通过返回的对象与被依赖类进行交互。

@startuml

class User {
  + username: string
}

class UserFactory {
  + createUser(name: string): User
}

UserFactory ----> User: returns as result

@enduml

image-20250731002256110

图中关系说明:用户工厂(UserFactory)类的createUser方法返回用户(User)类的对象,表明用户工厂在创建用户时依赖于用户类,箭头从 UserFactory 指向 User。

#include <iostream>
#include <string>
using namespace std;

class User {
private:
    string username;
public:
    User(string name) : username(name) {}
    string getUsername() { return username; }
};

class UserFactory {
public:
    // 方法返回User对象,形成依赖关系
    User createUser(string name) {
        return User(name);
    }
};

// 演示返回值中的依赖关系
int main() {
    UserFactory factory;
    User user = factory.createUser("john_doe");  // 通过返回值获取User对象

    cout << "Created user: " << user.getUsername() << endl;

    return 0;
}

输出结果:

Created user: john_doe

返回值依赖的代码特征

  • 依赖类(UserFactory)的方法返回类型为被依赖类(User);

  • 依赖关系体现在方法的返回过程中,为调用者提供了与被依赖类交互的途径;

  • 依赖类负责创建被依赖类的对象并返回,自身并不长期持有该对象。

依赖关系的应用场景总结

  • 方法参数中的依赖适用于依赖类需要外部提供被依赖类对象来完成操作的场景,如打印机打印特定文档,需要外部传入文档对象。

  • 局部变量中的依赖适用于依赖类在内部自主创建和使用被依赖类对象的场景,如文件处理器在处理文件时,内部创建日志记录器记录过程。

  • 返回值中的依赖适用于依赖类作为工厂或创建者,负责生成并返回被依赖类对象的场景,如用户工厂创建用户对象并返回。

正确理解和运用依赖关系,能够降低类之间的耦合度,使系统更加灵活、易于维护和扩展。在设计时,应根据类之间的交互需求,合理选择依赖关系的表现形式,避免不必要的长期关联,以提高代码的可复用性和可扩展性。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇