Menu
CoddyTech

组合与继承

本课属于 Coddy C++ 学习旅程的“面向对象编程”单元。第 76 课,共 104 课。

设计类之间的关系时,你主要有两种选择:继承(“is-a”)和组合(“has-a”)。选择合适的方式会显著影响代码的灵活性和可维护性。

继承会让类之间产生紧密耦合。继承自 Vehicle 的 Car 会永久绑定在这种关系中:

class Vehicle {
public:
    virtual void start() { std::cout << "Starting vehicle\n"; }
};

class Car : public Vehicle {
public:
    void start() override { std::cout << "Starting car\n"; }
};

组合将对象作为成员嵌入,从而建立更灵活的关系。Car 拥有一个 Engine,而不是自身就是一个引擎:

class Engine {
public:
    void start() { std::cout << "Engine running\n"; }
};

class Car {
private:
    Engine engine;  // Car 拥有一个 Engine
public:
    void start() { engine.start(); }
};

组合的主要优势是灵活性。你可以轻松替换组件,在运行时更改行为,或组合多种功能,而不受僵化类层次结构的限制。在大多数情况下,现代 C++ 设计更倾向于组合,而不是继承。

当确实存在“是一种”的关系,并且你需要多态时,使用继承。当你想复用功能,或者用“拥有”或“使用”关系来描述会更合适时,使用组合。

challenge icon

挑战

简单

我们来构建一个计算机系统模拟器,演示何时应该使用组合而不是继承。你将模拟一台拥有组件的计算机(组合),而不是一台本身就是某个组件的计算机,从而展示组合如何让你在运行时灵活地更换部件。

你会把代码分成 3 个文件:

  • Components.h:定义计算机可以包含的各个组件。

    创建一个 CPU 类,包含私有的 std::string model 和 int cores。提供一个同时接收这两个值的构造函数、一个用于打印 CPU [model] processing with [cores] cores 的 process() 方法,以及一个 getModel() 方法。

    创建一个 Memory 类,包含私有的 int sizeGB。提供一个构造函数、一个用于打印 Loading data into [sizeGB]GB RAM 的 load() 方法,以及一个 getSize() 方法。

    创建一个 Storage 类,包含私有的 std::string type(例如 "SSD" 或 "HDD")和 int capacityGB。提供一个构造函数、一个用于打印 Reading from [capacityGB]GB [type] 的 read() 方法,以及用于获取这两个字段的 getter 方法。

  • Computer.h:定义一个使用组合来组合各个组件的 Computer 类。

    你的 Computer 应包含私有成员对象:一个 CPU、一个 Memory 和一个 Storage。使用构造函数初始化列表,根据参数初始化这些组件。

    实现以下方法:

    • boot():打印 Booting computer...,然后依次调用内存的 load()、存储设备的 read() 和 CPU 的 process(),每次调用分别占一行
    • specs():按照以下格式打印计算机的规格:
      Computer Specs:
      - CPU: [model] ([cores] cores)
      - RAM: [size]GB
      - Storage: [capacity]GB [type]
  • main.cpp:读取 6 个输入(每个输入各占一行):
    1. CPU 型号(字符串)
    2. CPU 核心数(整数)
    3. 内存大小,单位为 GB(整数)
    4. 存储类型(字符串,例如 "SSD")
    5. 存储容量,单位为 GB(整数)
    6. 命令:"boot" 或 "specs"

    创建各个组件对象,然后使用组合创建一个 Computer。根据命令调用 boot() 或 specs()。

例如,输入 Intel i7、8、16、SSD、512 和 boot 时:

Booting computer...
Loading data into 16GB RAM
Reading from 512GB SSD
CPU Intel i7 processing with 8 cores

输入 AMD Ryzen、6、32、HDD、1000 和 specs 时:

Computer Specs:
- CPU: AMD Ryzen (6 cores)
- RAM: 32GB
- Storage: 1000GB HDD

请注意,Computer 类不会继承任何组件,而是包含它们。这种“拥有”关系意味着你可以轻松创建由不同组件组合而成的计算机;如果之后添加 setter 方法,甚至还可以更换组件。这种灵活性正是组合相较于继承的主要优势。

亲手试试

#include <iostream>
#include <string>
#include "Computer.h"

using namespace std;

int main() {
    // 读取输入
    string cpuModel;
    getline(cin, cpuModel);  // CPU 型号名称(可能包含空格)
    
    int cpuCores;
    cin >> cpuCores;  // CPU 核心数
    
    int memorySize;
    cin >> memorySize;  // 内存大小,单位为 GB
    
    string storageType;
    cin >> storageType;  // 存储类型(例如 "SSD")
    
    int storageCapacity;
    cin >> storageCapacity;  // 存储容量,单位为 GB
    
    string command;
    cin >> command;  // 命令:"boot" 或 "specs"

    // TODO: 创建组件对象(CPU、Memory、Storage)
    
    // TODO: 使用组合创建 Computer 对象
    
    // TODO: 根据命令调用 boot() 或 specs()

    return 0;
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: C++ 在线编译器