Menu
Coddy logo textTech

Kompositmuster

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

Das Composite-Muster ermöglicht es dir, einzelne Objekte und Objektgruppen einheitlich zu behandeln. Es setzt Objekte zu Baumstrukturen zusammen, in denen sowohl einzelne Elemente als auch Container von Elementen dieselbe Schnittstelle verwenden. Dies eignet sich ideal zur Darstellung von Hierarchien wie Dateisystemen, Organigrammen oder UI-Komponenten.

Das Muster besteht aus drei wesentlichen Teilen: einer Component-Schnittstelle, die gemeinsame Operationen definiert, Leaf-Klassen, die einzelne Objekte darstellen, und Composite-Klassen, die untergeordnete Elemente enthalten und Operationen an sie delegieren:

#include <iostream>
#include <memory>
#include <vector>
#include <string>

// Komponenten-Schnittstelle
class FileSystemItem {
public:
    virtual void display(int indent = 0) const = 0;
    virtual int getSize() const = 0;
    virtual ~FileSystemItem() = default;
};

// Leaf - repräsentiert einzelne Dateien
class File : public FileSystemItem {
    std::string name;
    int size;
public:
    File(const std::string& n, int s) : name(n), size(s) {}
    
    void display(int indent = 0) const override {
        std::cout << std::string(indent, ' ') << name 
                  << " (" << size << " KB)\n";
    }
    int getSize() const override { return size; }
};

// Composite - enthält andere Komponenten
class Folder : public FileSystemItem {
    std::string name;
    std::vector<std::shared_ptr<FileSystemItem>> children;
public:
    Folder(const std::string& n) : name(n) {}
    
    void add(std::shared_ptr<FileSystemItem> item) {
        children.push_back(item);
    }
    
    void display(int indent = 0) const override {
        std::cout << std::string(indent, ' ') << "[" << name << "]\n";
        for (const auto& child : children) {
            child->display(indent + 2);
        }
    }
    
    int getSize() const override {
        int total = 0;
        for (const auto& child : children) {
            total += child->getSize();
        }
        return total;
    }
};

Das Folder-Composite speichert untergeordnete Elemente und implementiert Operationen, indem es über sie iteriert. Wenn du getSize() für einen Ordner aufrufst, berechnet es rekursiv die Gesamtgröße aller enthaltenen Elemente. Der Clientcode muss nicht wissen, ob er mit einer Datei oder einem Ordner arbeitet – beide reagieren auf dieselbe Schnittstelle.

Verwende Composite, wenn du Teil-Ganzes-Hierarchien darstellen musst und möchtest, dass Clients einzelne Objekte und Kompositionen einheitlich behandeln.

challenge icon

Aufgabe

Einfach

Erstellen wir ein Organigramm-System mithilfe des Composite-Musters. Du erstellst eine Hierarchie, in der sowohl einzelne Mitarbeiter als auch Abteilungen (die weitere Mitarbeiter oder Unterabteilungen enthalten) einheitlich behandelt werden können. Dies entspricht der Struktur realer Unternehmen: Abteilungen enthalten Personen und andere Abteilungen und bilden so eine Baumstruktur.

Du organisierst deinen Code auf drei Dateien verteilt:

  • OrgComponent.h: Definiere die Komponentenschnittstelle, die sowohl Mitarbeiter als auch Abteilungen implementieren werden.

    Erstelle eine abstrakte Klasse OrgComponent mit:

    • getName(): gibt den Namen der Komponente zurück
    • getSalary(): gibt das Gesamtgehalt zurück (bei Mitarbeitern ihr eigenes Gehalt, bei Abteilungen die Summe aller enthaltenen Gehälter)
    • display(int indent = 0): zeigt die Komponente mit der passenden Einrückung an

    Füge einen virtuellen Destruktor hinzu.

  • Organization.h: Implementiere die Leaf- und Composite-Klassen.

    Erstelle eine Klasse Employee (das Leaf), die einen Namen und ein Gehalt speichert. Ihre Methode display() soll die Informationen des Mitarbeiters in folgendem Format ausgeben:

    [indent spaces]- [name] ($[salary])

    Erstelle eine Klasse Department (das Composite), die einen Namen und eine Sammlung von OrgComponent-Kindern mithilfe von std::shared_ptr speichert. Implementiere:

    • add(std::shared_ptr<OrgComponent> component): fügt der Abteilung ein Kind hinzu
    • getSalary(): berechnet rekursiv das Gesamtgehalt aller Mitglieder
    • display(): gibt den Abteilungsnamen in eckigen Klammern aus und zeigt anschließend alle Kinder mit erhöhter Einrückung an (füge pro Ebene 2 Leerzeichen hinzu)

    Das Ausgabeformat der Abteilung soll folgendermaßen aussehen:

    [indent spaces][Department Name]
      [children displayed with indent + 2]
  • main.cpp: Erstelle eine Organisationsstruktur und zeige sie an.

    Lies vier Eingaben ein:

    1. Unternehmensname (String)
    2. Abteilungsname (String)
    3. Name und Gehalt des ersten Mitarbeiters (Format: name,salary)
    4. Name und Gehalt des zweiten Mitarbeiters (Format: name,salary)

    Erstelle diese Struktur: Erstelle ein Unternehmen (eine Abteilung der obersten Ebene), füge ihm eine Unterabteilung hinzu und füge dieser Unterabteilung beide Mitarbeiter hinzu. Zeige anschließend die gesamte Organisation an und gib das Gesamtgehalt des Unternehmens aus.

    Gib nach der Anzeige der Struktur Folgendes aus:

    Total Salary: $[amount]

Beispielsweise mit den Eingaben TechCorp, Engineering, Alice,75000 und Bob,65000:

[TechCorp]
  [Engineering]
    - Alice ($75000)
    - Bob ($65000)
Total Salary: $140000

Mit den Eingaben StartupInc, Development, Carol,80000 und Dave,70000:

[StartupInc]
  [Development]
    - Carol ($80000)
    - Dave ($70000)
Total Salary: $150000

Beachte, wie getSalary() einheitlich funktioniert, unabhängig davon, ob die Methode auf einem Mitarbeiter oder einer Abteilung aufgerufen wird: Die Abteilung summiert automatisch die Gehälter aller ihrer Mitglieder. Der Client-Code muss beim Berechnen von Summen oder Anzeigen der Hierarchie nicht zwischen einzelnen Mitarbeitern und ganzen Abteilungen unterscheiden.

Probier es selbst

#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include "Organization.h"

int main() {
    // Eingaben lesen
    std::string companyName;
    std::string departmentName;
    std::string employee1Input;
    std::string employee2Input;
    
    std::getline(std::cin, companyName);
    std::getline(std::cin, departmentName);
    std::getline(std::cin, employee1Input);
    std::getline(std::cin, employee2Input);
    
    // Hilfs-Lambda zum Parsen des Formats "name,salary"
    auto parseEmployee = [](const std::string& input) -> std::pair<std::string, int> {
        size_t commaPos = input.find(',');
        std::string name = input.substr(0, commaPos);
        int salary = std::stoi(input.substr(commaPos + 1));
        return {name, salary};
    };
    
    auto [name1, salary1] = parseEmployee(employee1Input);
    auto [name2, salary2] = parseEmployee(employee2Input);
    
    // TODO: Erstelle das Unternehmen als Top-Level-Department
    
    // TODO: Erstelle eine Unterabteilung
    
    // TODO: Erstelle zwei Employee-Objekte mit den geparsten Daten
    
    // TODO: Füge die Mitarbeiter zur Unterabteilung hinzu
    
    // TODO: Füge die Unterabteilung zum Unternehmen hinzu
    
    // TODO: Zeige die gesamte Organisationsstruktur an
    
    // TODO: Gib das Gesamtgehalt im Format aus: Total Salary: $[amount]
    
    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

Übe selbstständig: Online-C++-Compiler