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.
Aufgabe
EinfachLassen 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
OrgComponentmit:getName()— gibt den Namen der Komponente zurückgetSalary()— 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 Methodedisplay()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 vonOrgComponent-Kindern unter Verwendung vonstd::shared_ptrspeichert. Implementieren Sie:add(std::shared_ptr<OrgComponent> component)— fügt der Abteilung ein Kind hinzugetSalary()— berechnet rekursiv das Gesamtgehalt aller Mitgliederdisplay()— 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:
- Unternehmensname (String)
- Abteilungsname (String)
- Name und Gehalt des ersten Mitarbeiters (Format:
name,salary) - 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: $140000Mit den Eingaben StartupInc, Development, Carol,80000 und Dave,70000:
[StartupInc]
[Development]
- Carol ($80000)
- Dave ($70000)
Total Salary: $150000Beachten 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;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner11Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungMixins via CRTPPimpl-IdiomType ErasureEnum-Klassen & starke TypisierungException-Handling in OOPEigene Exception-Hierarchien14Entwurfsmuster Teil 2
Command-MusterAdapter-MusterDecorator-MusterTemplate-Method-MusterState-MusterComposite-MusterRAII als Muster3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container