Menu
Coddy logo textTech

Komposition vs. Vererbung

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 76 von 104.

Beim Entwurf von Beziehungen zwischen Klassen haben Sie zwei Hauptoptionen: Vererbung („ist-ein“) und Komposition („hat-ein“). Die Wahl des richtigen Ansatzes hat erhebliche Auswirkungen auf die Flexibilität und Wartbarkeit Ihres Codes.

Vererbung erzeugt eine enge Kopplung zwischen Klassen. Ein Car, das von Vehicle erbt, ist dauerhaft an diese Beziehung gebunden:

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

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

Komposition bettet Objekte als Member ein, wodurch eine flexiblere Beziehung entsteht. Ein Car hat einen Engine, anstatt ein Motor zu sein:

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

class Car {
private:
    Engine engine;  // Auto HAT einen Motor
public:
    void start() { engine.start(); }
};

Der entscheidende Vorteil der Komposition ist Flexibilität. Sie können Komponenten einfach austauschen, das Verhalten zur Laufzeit ändern oder mehrere Funktionen kombinieren, ohne die Einschränkungen einer starren Klassenhierarchie. Modernes C++-Design bevorzugt in den meisten Fällen Komposition gegenüber Vererbung.

Verwenden Sie Vererbung, wenn eine echte „is-a“-Beziehung besteht und Sie Polymorphismus benötigen. Verwenden Sie Komposition, wenn Sie Funktionalität wiederverwenden möchten oder wenn die Beziehung besser als „has-a“ oder „uses-a“ beschrieben wird.

challenge icon

Aufgabe

Einfach

Lassen Sie uns einen Computersystem-Simulator bauen, der demonstriert, wann Komposition gegenüber Vererbung bevorzugt werden sollte. Sie werden einen Computer modellieren, der Komponenten besitzt (Komposition), anstatt selbst eine Komponente zu sein. Dies zeigt, wie Komposition die Flexibilität bietet, Teile zur Laufzeit auszutauschen.

Sie werden Ihren Code in drei Dateien organisieren:

  • Components.h: Definieren Sie die einzelnen Komponenten, die ein Computer enthalten kann.

    Erstellen Sie eine CPU-Klasse mit einem privaten std::string model und int cores. Stellen Sie einen Konstruktor bereit, der beide Werte entgegennimmt, eine process()-Methode, die CPU [model] processing with [cores] cores ausgibt, und eine getModel()-Methode.

    Erstellen Sie eine Memory-Klasse mit einem privaten int sizeGB. Stellen Sie einen Konstruktor bereit, eine load()-Methode, die Loading data into [sizeGB]GB RAM ausgibt, und eine getSize()-Methode.

    Erstellen Sie eine Storage-Klasse mit einem privaten std::string type (wie "SSD" oder "HDD") und int capacityGB. Stellen Sie einen Konstruktor bereit, eine read()-Methode, die Reading from [capacityGB]GB [type] ausgibt, und Getter-Methoden für beide Felder.

  • Computer.h: Definieren Sie eine Computer-Klasse, die Komposition verwendet, um Komponenten zu kombinieren.

    Ihr Computer sollte private Member-Objekte haben: eine CPU, ein Memory und ein Storage. Verwenden Sie die Konstruktor-Initialisierungsliste, um diese Komponenten aus den Parametern zu initialisieren.

    Implementieren Sie diese Methoden:

    • boot() — gibt Booting computer... aus und ruft dann load() für den Speicher, read() für den Datenträger und process() für die CPU auf, jeweils in einer eigenen Zeile.
    • specs() — gibt die Spezifikationen des Computers in diesem Format aus:
      Computer Specs:
      - CPU: [model] ([cores] cores)
      - RAM: [size]GB
      - Storage: [capacity]GB [type]
  • main.cpp: Lesen Sie sechs Eingaben ein (jede in einer eigenen Zeile):
    1. CPU-Modellname (String)
    2. Anzahl der CPU-Kerne (Integer)
    3. Speichergröße in GB (Integer)
    4. Speichertyp (String, z. B. "SSD")
    5. Speicherkapazität in GB (Integer)
    6. Ein Befehl: entweder "boot" oder "specs"

    Erstellen Sie die Komponentenobjekte und erstellen Sie dann einen Computer mittels Komposition. Rufen Sie je nach Befehl entweder boot() oder specs() auf.

Zum Beispiel mit den Eingaben Intel i7, 8, 16, SSD, 512 und boot:

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

Mit den Eingaben AMD Ryzen, 6, 32, HDD, 1000 und specs:

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

Beachten Sie, dass die Computer-Klasse nicht von einer Komponente erbt – sie enthält sie. Diese "Hat-ein"-Beziehung bedeutet, dass Sie problemlos Computer mit verschiedenen Komponentenkombinationen erstellen oder Komponenten später sogar austauschen könnten, wenn Sie Setter-Methoden hinzufügen würden. Diese Flexibilität ist der entscheidende Vorteil der Komposition gegenüber der Vererbung.

Spickzettel

Wählen Sie beim Entwurf von Beziehungen zwischen Klassen zwischen Vererbung („is-a“) und Komposition („has-a“).

Vererbung erzeugt eine enge Kopplung zwischen Klassen:

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

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

Komposition bettet Objekte als Member ein und schafft so flexible Beziehungen:

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

class Car {
private:
    Engine engine;  // Car HAS an Engine
public:
    void start() { engine.start(); }
};

Wann man was verwendet:

  • Verwenden Sie Vererbung für echte „is-a“-Beziehungen, wenn Sie Polymorphie benötigen
  • Verwenden Sie Komposition, um Funktionalität wiederzuverwenden oder für „has-a“/„uses-a“-Beziehungen
  • Modernes C++-Design bevorzugt Komposition wegen ihrer Flexibilität – Sie können Komponenten austauschen, das Verhalten zur Laufzeit ändern und Funktionen kombinieren, ohne starre Hierarchien zu schaffen

Probier es selbst

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

using namespace std;

int main() {
    // Eingaben lesen
    string cpuModel;
    getline(cin, cpuModel);  // CPU-Modellname (kann Leerzeichen enthalten)
    
    int cpuCores;
    cin >> cpuCores;  // Anzahl der CPU-Kerne
    
    int memorySize;
    cin >> memorySize;  // Speichergröße in GB
    
    string storageType;
    cin >> storageType;  // Speichertyp (z. B. "SSD")
    
    int storageCapacity;
    cin >> storageCapacity;  // Speicherkapazität in GB
    
    string command;
    cin >> command;  // Befehl: "boot" oder "specs"

    // TODO: Komponentenobjekte erstellen (CPU, Memory, Storage)
    
    // TODO: Ein Computer-Objekt mittels Komposition erstellen
    
    // TODO: Basierend auf dem Befehl entweder boot() oder specs() aufrufen

    return 0;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung