Menu
Coddy logo textTech

Композиция против наследования

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 76 из 104.

При проектировании отношений между классами у вас есть два основных варианта: наследование («is-a») и composition («has-a»). Правильный выбор подхода существенно влияет на гибкость и сопровождаемость вашего кода.

Наследование создаёт тесную связь между классами. Car, который наследует от Vehicle, навсегда связан с этим отношением:

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, а не является engine:

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

class Car {
private:
    Engine engine;  // Car ИМЕЕТ Engine
public:
    void start() { engine.start(); }
};

Ключевое преимущество composition — гибкость. Вы можете легко заменять компоненты, изменять поведение во время выполнения или объединять несколько возможностей без ограничений жёсткой иерархии class. В современном дизайне C++ в большинстве случаев composition предпочитают наследованию.

Используй наследование, когда существует подлинное отношение «является» и тебе нужен полиморфизм. Используй композицию, когда хочешь повторно использовать функциональность или когда отношение лучше описывается как «has-a» или «uses-a».

challenge icon

Задание

Легко

Давайте создадим симулятор компьютерной системы, демонстрирующий, когда следует использовать composition вместо наследования. Вы смоделируете компьютер, который has компоненты (composition), а не является компонентом, показывая, как composition обеспечивает гибкость для замены частей во время выполнения.

Вы организуете код в трёх файлах:

  • Components.h: Определите отдельные компоненты, которые может contain компьютер.

    Создайте класс CPU с приватными полями std::string model и int cores. Добавьте конструктор, принимающий оба значения, метод process(), который выводит CPU [model] processing with [cores] cores, и метод getModel().

    Создайте класс Memory с приватным полем int sizeGB. Добавьте конструктор, метод load(), который выводит Loading data into [sizeGB]GB RAM, и метод getSize().

    Создайте класс Storage с приватным полем std::string type (например, "SSD" или "HDD") и полем int capacityGB. Добавьте конструктор, метод read(), который выводит Reading from [capacityGB]GB [type], и getter-методы для обоих полей.

  • Computer.h: Определите класс Computer, использующий composition для объединения компонентов.

    Ваш Computer должен иметь приватные объекты-члены: CPU, Memory и Storage. Используйте список инициализации конструктора, чтобы инициализировать эти компоненты из параметров.

    Implement следующие методы:

    • boot(): выводит Booting computer..., затем вызывает load() для памяти, read() для хранилища и process() для CPU, каждый вызов — в отдельной строке
    • specs(): выводит характеристики компьютера в следующем формате:
      Computer Specs:
      - CPU: [model] ([cores] cores)
      - RAM: [size]GB
      - Storage: [capacity]GB [type]
  • main.cpp: Считайте шесть входных значений (каждое в отдельной строке):
    1. Имя модели CPU (строка)
    2. Количество ядер CPU (целое число)
    3. Объём памяти в GB (целое число)
    4. Тип хранилища (строка, например, "SSD")
    5. Ёмкость хранилища в GB (целое число)
    6. Команду: либо "boot", либо "specs"

    Создайте объекты компонентов, затем создайте Computer с использованием composition. В зависимости от команды вызовите либо 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 не наследуется ни от одного компонента. Он содержит их. Эта связь «has-a» означает, что вы можете легко создавать компьютеры с различными комбинациями компонентов или даже заменять компоненты позднее, если добавите setter-методы. Эта гибкость является главным преимуществом composition перед наследованием.

Попробуйте сами

#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;  // Размер памяти в ГБ
    
    string storageType;
    cin >> storageType;  // Тип хранилища (например, "SSD")
    
    int storageCapacity;
    cin >> storageCapacity;  // Ёмкость хранилища в ГБ
    
    string command;
    cin >> command;  // Команда: "boot" или "specs"

    // TODO: Создать объекты компонентов (CPU, Memory, Storage)
    
    // TODO: Создать объект Computer с использованием композиции
    
    // TODO: В зависимости от команды вызвать либо boot(), либо specs()

    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор C++