组合与继承
本课属于 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++ 设计更倾向于组合,而不是继承。
当确实存在“是一种”的关系,并且你需要多态时,使用继承。当你想复用功能,或者用“拥有”或“使用”关系来描述会更合适时,使用组合。
挑战
简单我们来构建一个计算机系统模拟器,演示何时应该使用组合而不是继承。你将模拟一台拥有组件的计算机(组合),而不是一台本身就是某个组件的计算机,从而展示组合如何让你在运行时灵活地更换部件。
你会把代码分成 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 个输入(每个输入各占一行):- CPU 型号(字符串)
- CPU 核心数(整数)
- 内存大小,单位为 GB(整数)
- 存储类型(字符串,例如 "SSD")
- 存储容量,单位为 GB(整数)
- 命令:"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;
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: C++ 在线编译器