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.
Sfida
FacileCostruiamo 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
OrgComponentcon:getName(): restituisce il nome del componentegetSalary(): 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 metododisplay()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 figliOrgComponentusandostd::shared_ptr. Implementa:add(std::shared_ptr<OrgComponent> component): aggiunge un figlio al repartogetSalary(): calcola ricorsivamente lo stipendio totale di tutti i membridisplay(): 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:
- Nome dell’azienda (stringa)
- Nome del reparto (stringa)
- Nome e stipendio del primo dipendente (formato:
name,salary) - 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: $140000Con gli input StartupInc, Development, Carol,80000 e Dave,70000:
[StartupInc]
[Development]
- Carol ($80000)
- Dave ($70000)
Total Salary: $150000Nota 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;
}
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
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online