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.
Aufgabe
EinfachErstellen 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
OrgComponentmit:getName(): gibt den Namen der Komponente zurückgetSalary(): 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 Methodedisplay()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 vonOrgComponent-Kindern mithilfe vonstd::shared_ptrspeichert. Implementiere:add(std::shared_ptr<OrgComponent> component): fügt der Abteilung ein Kind hinzugetSalary(): berechnet rekursiv das Gesamtgehalt aller Mitgliederdisplay(): 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:
- Unternehmensname (String)
- Abteilungsname (String)
- Name und Gehalt des ersten Mitarbeiters (Format:
name,salary) - 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: $140000Mit den Eingaben StartupInc, Development, Carol,80000 und Dave,70000:
[StartupInc]
[Development]
- Carol ($80000)
- Dave ($70000)
Total Salary: $150000Beachte, 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;
}
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 & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator11Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungMixins mittels CRTPPimpl-IdiomTyplöschungEnum-Klassen & starke TypisierungAusnahmebehandlung in OOPBenutzerdefinierte Ausnahmehierarchien14Entwurfsmuster Teil 2
BefehlsmusterAdaptermusterDekorierermusterTemplate-Method-MusterZustandsmusterKompositmusterRAII als Muster3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler