Menu
Coddy logo textTech

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

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

При проектировании отношений между классами у вас есть два основных варианта: наследование ("is-a") и композиция ("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, а не является двигателем:

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

class Car {
private:
    Engine engine;  // Car СОДЕРЖИТ Engine
public:
    void start() { engine.start(); }
};

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

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

challenge icon

Задание

Легко

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

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

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

    Создайте класс 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], и геттеры для обоих полей.

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

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

    Реализуйте следующие методы:

    • boot() — выводит Booting computer..., затем вызывает load() у памяти, read() у хранилища и process() у процессора, каждый вызов на новой строке.
    • specs() — выводит технические характеристики компьютера в следующем формате:
      Computer Specs:
      - CPU: [model] ([cores] cores)
      - RAM: [size]GB
      - Storage: [capacity]GB [type]
  • main.cpp: Считайте шесть входных значений (каждое на отдельной строке):
    1. Название модели процессора (string)
    2. Количество ядер процессора (integer)
    3. Объем оперативной памяти в ГБ (integer)
    4. Тип хранилища (string, например, "SSD")
    5. Емкость хранилища в ГБ (integer)
    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 не наследуется ни от одного компонента — он содержит их. Это отношение «имеет» (has-a) означает, что вы можете легко создавать компьютеры с различными комбинациями компонентов или даже заменять компоненты позже, если добавите методы-сеттеры. Эта гибкость является ключевым преимуществом композиции перед наследованием.

Шпаргалка

При проектировании отношений между классами выбирайте между наследованием ("is-a") и композицией ("has-a").

Наследование создает сильную связанность между классами:

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

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

Композиция встраивает объекты как члены, создавая гибкие отношения:

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

class Car {
private:
    Engine engine;  // У Car есть Engine
public:
    void start() { engine.start(); }
};

Когда использовать каждый из подходов:

  • Используйте наследование для подлинных отношений "is-a", когда вам нужен полиморфизм
  • Используйте композицию для повторного использования функциональности или для отношений "has-a"/"uses-a"
  • Современный дизайн на C++ отдает предпочтение композиции из-за ее гибкости — вы можете заменять компоненты, изменять поведение во время выполнения и комбинировать возможности без жестких иерархий

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

#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Проверьте себя

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

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