Menu
Coddy logo textTech

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.

challenge icon

Wyzwanie

Łatwy

Zbudujmy 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ę OrgComponent z metodami:

    • getName(): zwraca nazwę komponentu
    • getSalary(): 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 metoda display() 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 typu OrgComponent za pomocą std::shared_ptr. Zaimplementuj:

    • add(std::shared_ptr<OrgComponent> component): dodaje dziecko do działu
    • getSalary(): rekurencyjnie oblicza łączne wynagrodzenie wszystkich członków
    • display(): 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:

    1. Nazwa firmy (ciąg znaków)
    2. Nazwa działu (ciąg znaków)
    3. Imię i nazwisko oraz wynagrodzenie pierwszego pracownika (format: name,salary)
    4. 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: $140000

Dla danych wejściowych StartupInc, Development, Carol,80000 i Dave,70000:

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

Zwróć 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;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online