Menu
Coddy logo textTech

Pattern Composite

Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 101 di 104.

Il pattern Composite ti permette di trattare in modo uniforme i singoli oggetti e i gruppi di oggetti. Compone gli oggetti in strutture ad albero in cui sia i singoli elementi sia i contenitori di elementi condividono la stessa interfaccia. È ideale per rappresentare gerarchie come file system, organigrammi o componenti dell’interfaccia utente.

Il pattern ha tre parti fondamentali: un'interfaccia Component che definisce le operazioni comuni, classi Leaf che rappresentano oggetti individuali e classi Composite che contengono elementi figli e delegano loro le operazioni:

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

// Interfaccia del componente
class FileSystemItem {
public:
    virtual void display(int indent = 0) const = 0;
    virtual int getSize() const = 0;
    virtual ~FileSystemItem() = default;
};

// Foglia - rappresenta i singoli file
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; }
};

// Composito - contiene altri componenti
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;
    }
};

Il composito Folder memorizza gli elementi figli e implementa le operazioni iterando su di essi. Quando chiami getSize() su una cartella, calcola ricorsivamente la dimensione totale di tutti gli elementi contenuti. Il codice client non deve sapere se sta lavorando con un file o una cartella: entrambi rispondono alla stessa interfaccia.

Usa Composite quando hai bisogno di rappresentare gerarchie parte-tutto e vuoi che i client trattino in modo uniforme gli oggetti individuali e le composizioni.

challenge icon

Sfida

Facile

Costruiamo un sistema di organigramma usando il pattern Composite. Creerai una gerarchia in cui sia i singoli dipendenti sia i reparti (che contengono altri dipendenti o sottoreparti) possono essere trattati in modo uniforme. Questo rispecchia la struttura delle aziende reali: i reparti contengono persone e altri reparti, formando una struttura ad albero.

Organizzerai il codice in tre file:

  • OrgComponent.h: definisci l’interfaccia del componente che implementeranno sia i dipendenti sia i reparti.

    Crea una classe astratta OrgComponent con:

    • getName(): restituisce il nome del componente
    • getSalary(): restituisce lo stipendio totale (per i dipendenti, il loro stipendio; per i reparti, la somma di tutti gli stipendi contenuti)
    • display(int indent = 0): visualizza il componente con la corretta indentazione

    Includi un distruttore virtuale.

  • Organization.h: implementa le classi foglia e composita.

    Crea una classe Employee (la foglia) che memorizza un nome e uno stipendio. Il suo metodo display() dovrebbe stampare le informazioni del dipendente in questo formato:

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

    Crea una classe Department (il composito) che memorizza un nome e una raccolta di figli OrgComponent usando std::shared_ptr. Implementa:

    • add(std::shared_ptr<OrgComponent> component): aggiunge un figlio al reparto
    • getSalary(): calcola ricorsivamente lo stipendio totale di tutti i membri
    • display(): stampa il nome del reparto tra parentesi quadre, poi visualizza tutti i figli con un’indentazione maggiore (aggiungi 2 spazi per livello)

    Il formato di visualizzazione del reparto dovrebbe essere:

    [indent spaces][Department Name]
      [children displayed with indent + 2]
  • main.cpp: costruisci e visualizza una struttura organizzativa.

    Leggi quattro input:

    1. Nome dell’azienda (stringa)
    2. Nome del reparto (stringa)
    3. Nome e stipendio del primo dipendente (formato: name,salary)
    4. Nome e stipendio del secondo dipendente (formato: name,salary)

    Costruisci questa struttura: crea un’azienda (reparto di livello superiore), aggiungi un sottoreparto e aggiungi entrambi i dipendenti a quel sottoreparto. Poi visualizza l’intera organizzazione e stampa lo stipendio totale dell’azienda.

    Dopo aver visualizzato la struttura, stampa:

    Total Salary: $[amount]

Per esempio, con gli input TechCorp, Engineering, Alice,75000 e Bob,65000:

[TechCorp]
  [Engineering]
    - Alice ($75000)
    - Bob ($65000)
Total Salary: $140000

Con gli input StartupInc, Development, Carol,80000 e Dave,70000:

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

Nota come getSalary() funzioni in modo uniforme sia quando viene chiamato su un dipendente sia quando viene chiamato su un reparto: il reparto aggrega automaticamente gli stipendi di tutti i suoi membri. Il codice client non deve distinguere tra singoli dipendenti e interi reparti quando calcola i totali o visualizza la gerarchia.

Provalo tu

#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include "Organization.h"

int main() {
    // Leggi gli input
    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);
    
    // Lambda di supporto per analizzare il formato "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: Crea l'azienda come Department di livello superiore
    
    // TODO: Crea un sottodipartimento
    
    // TODO: Crea due oggetti Employee usando i dati analizzati
    
    // TODO: Aggiungi i dipendenti al sottodipartimento
    
    // TODO: Aggiungi il sottodipartimento all'azienda
    
    // TODO: Visualizza l'intera struttura dell'organizzazione
    
    // TODO: Stampa lo stipendio totale nel formato: Total Salary: $[amount]
    
    return 0;
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore C++ online