Wzorzec kompozytu
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 101 z 104.
Wzorzec Composite pozwala traktować pojedyncze obiekty i grupy obiektów jednakowo. Łączy obiekty w struktury drzewiaste, w których zarówno pojedyncze elementy, jak i ich kontenery mają ten sam interfejs. To idealne rozwiązanie do przedstawiania hierarchii, takich jak systemy plików, schematy organizacyjne czy komponenty interfejsu użytkownika.
Ten wzorzec ma trzy kluczowe elementy: interfejs Component, który definiuje wspólne operacje, klasy Leaf reprezentujące poszczególne obiekty oraz klasy Composite, które zawierają elementy potomne i delegują im operacje:
#include <iostream>
#include <memory>
#include <vector>
#include <string>
// Interfejs komponentu
class FileSystemItem {
public:
virtual void display(int indent = 0) const = 0;
virtual int getSize() const = 0;
virtual ~FileSystemItem() = default;
};
// Liść – reprezentuje pojedyncze pliki
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; }
};
// Kompozyt – zawiera inne komponenty
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;
}
};Kompozyt Folder przechowuje elementy potomne i implementuje operacje, iterując po nich. Gdy wywołujesz getSize() na folderze, rekurencyjnie oblicza całkowity rozmiar wszystkich zawartych w nim elementów. Kod klienta nie musi wiedzieć, czy pracuje z plikiem, czy z folderem — oba odpowiadają na ten sam interfejs.
Użyj wzorca Composite, gdy chcesz przedstawić hierarchie część–całość i traktować pojedyncze obiekty oraz ich kompozycje w jednolity sposób.
Wyzwanie
ŁatwyZbudujmy system schematu organizacyjnego z użyciem wzorca Composite. Utworzysz hierarchię, w której zarówno poszczególnych pracowników, jak i działy (zawierające innych pracowników lub poddziały) można traktować jednolicie. Odzwierciedla to strukturę prawdziwych firm: działy zawierają pracowników i inne działy, tworząc strukturę drzewa.
Rozmieścisz swój kod w trzech plikach:
OrgComponent.h: Zdefiniuj interfejs komponentu, który zaimplementują zarówno pracownicy, jak i działy.Utwórz abstrakcyjną klasę
OrgComponentz metodami:getName(): zwraca nazwę komponentugetSalary(): zwraca łączne wynagrodzenie (w przypadku pracowników ich własne wynagrodzenie, a w przypadku działów sumę wynagrodzeń wszystkich zawartych w nich osób)display(int indent = 0): wyświetla komponent z odpowiednim wcięciem
Dodaj wirtualny destruktor.
Organization.h: Zaimplementuj klasy liścia i kompozytu.Utwórz klasę
Employee(liść), która przechowuje imię i nazwisko oraz wynagrodzenie. Jej metodadisplay()powinna wypisywać informacje o pracowniku w następującym formacie:[indent spaces]- [name] ($[salary])Utwórz klasę
Department(kompozyt), która przechowuje nazwę i kolekcję dzieci typuOrgComponentza pomocąstd::shared_ptr. Zaimplementuj:add(std::shared_ptr<OrgComponent> component): dodaje dziecko do działugetSalary(): rekurencyjnie oblicza łączne wynagrodzenie wszystkich członkówdisplay(): wypisuje nazwę działu w nawiasach kwadratowych, a następnie wyświetla wszystkie dzieci z większym wcięciem (dodaj 2 spacje na każdym poziomie)
Format wyświetlania działu powinien wyglądać następująco:
[indent spaces][Department Name] [children displayed with indent + 2]main.cpp: Zbuduj i wyświetl strukturę organizacji.Odczytaj cztery wartości wejściowe:
- Nazwa firmy (ciąg znaków)
- Nazwa działu (ciąg znaków)
- Imię i nazwisko oraz wynagrodzenie pierwszego pracownika (format:
name,salary) - Imię i nazwisko oraz wynagrodzenie drugiego pracownika (format:
name,salary)
Zbuduj następującą strukturę: utwórz firmę (dział najwyższego poziomu), dodaj do niej poddział, a następnie dodaj obu pracowników do tego poddziału. Potem wyświetl całą organizację i wypisz łączne wynagrodzenie w firmie.
Po wyświetleniu struktury wypisz:
Total Salary: $[amount]
Na przykład dla danych wejściowych TechCorp, Engineering, Alice,75000 i Bob,65000:
[TechCorp]
[Engineering]
- Alice ($75000)
- Bob ($65000)
Total Salary: $140000Dla danych wejściowych StartupInc, Development, Carol,80000 i Dave,70000:
[StartupInc]
[Development]
- Carol ($80000)
- Dave ($70000)
Total Salary: $150000Zwróć uwagę, że getSalary() działa jednolicie niezależnie od tego, czy wywołasz ją dla pracownika, czy działu: dział automatycznie sumuje wynagrodzenia wszystkich swoich członków. Kod klienta nie musi rozróżniać poszczególnych pracowników i całych działów podczas obliczania łącznych wynagrodzeń ani wyświetlania hierarchii.
Spróbuj swoich sił
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include "Organization.h"
int main() {
// Odczytaj dane wejściowe
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);
// Pomocnicza lambda do parsowania formatu "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: Utwórz firmę jako nadrzędny Department
// TODO: Utwórz poddział
// TODO: Utwórz dwa obiekty Employee, używając sparsowanych danych
// TODO: Dodaj pracowników do poddziału
// TODO: Dodaj poddział do firmy
// TODO: Wyświetl całą strukturę organizacji
// TODO: Wypisz łączną kwotę wynagrodzeń w formacie: Total Salary: $[amount]
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów11Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieMixiny z użyciem CRTPIdiom PimplWymazywanie typówKlasy enum i silne typowanieObsługa wyjątków w OOPWłasne hierarchie wyjątków14Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytuRAII jako wzorzec3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online