Menu
Coddy logo textTech

Composite-Muster

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

Das Composite-Muster ermöglicht es Ihnen, einzelne Objekte und Gruppen von Objekten einheitlich zu behandeln. Es setzt Objekte zu Baumstrukturen zusammen, in denen sowohl Einzelelemente als auch Container von Elementen dieselbe Schnittstelle teilen. Dies ist ideal für die Darstellung von Hierarchien wie Dateisystemen, Organigrammen oder UI-Komponenten.

Das Muster besteht aus drei Hauptteilen: einem Component-Interface, das gemeinsame Operationen definiert, Leaf-Klassen, die einzelne Objekte darstellen, und Composite-Klassen, die Kindelemente enthalten und Operationen an diese 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;
};

// Blatt - 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; }
};

// Kompositum - 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-Kompositum speichert Kindelemente und implementiert Operationen, indem es diese durchläuft. Wenn Sie getSize() auf einem Ordner aufrufen, berechnet es rekursiv die Gesamtgröße aller enthaltenen Elemente. Der Client-Code muss nicht wissen, ob er mit einer Datei oder einem Ordner arbeitet – beide reagieren auf dieselbe Schnittstelle.

Verwenden Sie Composite, wenn Sie Teil-Ganzes-Hierarchien darstellen müssen und möchten, dass Clients Einzelobjekte und Kompositionen einheitlich behandeln.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Organigramm-System unter Verwendung des Composite-Musters erstellen. Sie werden eine Hierarchie aufbauen, in der sowohl einzelne Mitarbeiter als auch Abteilungen (die andere Mitarbeiter oder Unterabteilungen enthalten) einheitlich behandelt werden können. Dies spiegelt die Struktur realer Unternehmen wider – Abteilungen enthalten Personen und andere Abteilungen und bilden so eine Baumstruktur.

Sie werden Ihren Code über drei Dateien organisieren:

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

    Erstellen Sie eine abstrakte Klasse OrgComponent mit:

    • getName() — gibt den Namen der Komponente zurück
    • getSalary() — gibt das Gesamtgehalt zurück (für Mitarbeiter ihr eigenes Gehalt; für Abteilungen die Summe aller enthaltenen Gehälter)
    • display(int indent = 0) — zeigt die Komponente mit korrekter Einrückung an

    Fügen Sie einen virtuellen Destruktor hinzu.

  • Organization.h: Implementieren Sie die Leaf- (Blatt) und Composite- (Kompositum) Klassen.

    Erstellen Sie eine Klasse Employee (das Blatt), die einen Namen und ein Gehalt speichert. Ihre Methode display() sollte die Informationen des Mitarbeiters in diesem Format ausgeben:

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

    Erstellen Sie eine Klasse Department (das Kompositum), die einen Namen und eine Sammlung von OrgComponent-Kindern unter Verwendung von std::shared_ptr speichert. Implementieren Sie:

    • 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 Klammern aus und zeigt dann alle Kinder mit erhöhter Einrückung an (fügen Sie 2 Leerzeichen pro Ebene hinzu)

    Das Anzeigeformat der Abteilung sollte wie folgt aussehen:

    [indent spaces][Department Name]
      [children displayed with indent + 2]
  • main.cpp: Erstellen und Anzeigen einer Organisationsstruktur.

    Lesen Sie 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)

    Bauen Sie diese Struktur auf: Erstellen Sie ein Unternehmen (Abteilung auf oberster Ebene), fügen Sie ihm eine Unterabteilung hinzu und fügen Sie beide Mitarbeiter dieser Unterabteilung hinzu. Zeigen Sie dann die gesamte Organisation an und geben Sie das Gesamtgehalt des Unternehmens aus.

    Geben Sie nach der Anzeige der Struktur Folgendes aus:

    Total Salary: $[amount]

Zum Beispiel 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

Beachten Sie, wie getSalary() einheitlich funktioniert, egal ob es für einen Mitarbeiter oder eine Abteilung aufgerufen wird – die Abteilung aggregiert automatisch die Gehälter aller ihrer Mitglieder. Der Client-Code muss nicht zwischen einzelnen Mitarbeitern und ganzen Abteilungen unterscheiden, wenn er Summen berechnet oder die Hierarchie anzeigt.

Spickzettel

Das Composite-Muster (Kompositum) behandelt Einzelobjekte und Gruppen von Objekten einheitlich, indem es sie zu Baumstrukturen zusammensetzt, in denen sowohl einzelne Elemente als auch Container dieselbe Schnittstelle teilen.

Das Muster besteht aus drei Hauptteilen:

  • Component: Eine Schnittstelle, die gemeinsame Operationen definiert
  • Leaf: Klassen, die einzelne Objekte darstellen
  • Composite: Klassen, die Kinder enthalten und Operationen an diese delegieren

Beispielimplementierung eines Dateisystems:

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

// Component-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 Composite speichert Kinder mittels std::shared_ptr und implementiert Operationen, indem es über diese iteriert. Operationen wie getSize() arbeiten rekursiv und berechnen die Gesamtsummen über die gesamte Hierarchie hinweg.

Verwenden Sie das Composite-Muster, wenn Sie Teil-Ganzes-Hierarchien darstellen müssen und möchten, dass Clients Einzelobjekte und Kompositionen einheitlich behandeln.

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 Department auf oberster Ebene
    
    // TODO: Erstelle eine Unterabteilung
    
    // TODO: Erstelle zwei Employee-Objekte unter Verwendung der geparsten Daten
    
    // TODO: Füge die Mitarbeiter der Unterabteilung hinzu
    
    // TODO: Füge die Unterabteilung dem Unternehmen hinzu
    
    // TODO: Zeige die gesamte Organisationsstruktur an
    
    // TODO: Gib das Gesamtgehalt im Format Total Salary: $[amount] aus
    
    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