La delega delle responsabilità tramite composizione è una pratica di programmazione in cui un oggetto di una classe contiene un oggetto di un'altra classe (o classi) e li utilizza per implementare parte della propria logica, invece di ereditare il loro interfaccia.
Le prime versioni della OOP si concentravano sull'ereditarietà, ma nel tempo la pratica ha dimostrato che la composizione offre spesso maggiore flessibilità, estensibilità e riduzione della coesione tra i componenti.
L'ereditarietà lega gli oggetti in modo rigido: le modifiche nella classe base influenzano tutti gli eredi, le gerarchie diventano complesse, si verifica una fragilità nell'architettura. La composizione elimina questi problemi permettendo di costruire sistemi più affidabili e manutenibili.
In C++, la delega viene implementata includendo l'oggetto di una classe come membro di un'altra classe. Nei metodi della classe wrapper vengono chiamati i metodi dell'oggetto incorporato.
Esempio di codice:
class Logger { public: void log(const std::string& msg) { std::cout << msg << std::endl; } }; class FileProcessor { Logger logger; // Composizione public: void process(const std::string& filename) { logger.log("Elaborazione del file: " + filename); // ... } };
Caratteristiche chiave:
La composizione può sostituire completamente l'ereditarietà?
No, l'ereditarietà è necessaria dove è richiesta la relazione "è" (is-a), la composizione è quando "ha" (has-a). Ad esempio, Button eredita da Widget, ma Car "ha" un Motore (composizione).
È possibile modificare il comportamento del metodo delegato nella composizione?
Sì, i metodi di delega possono essere adattati senza toccare la classe originale. Inoltre, è possibile cambiare dinamicamente l'oggetto delegato (ad esempio, tramite un puntatore o un puntatore unico).
La composizione è più lenta dell'ereditarietà?
No, nella maggior parte dei casi non c'è differenza nelle prestazioni. A volte l'ereditarietà aggiunge costi a causa delle chiamate virtuali (vtable), mentre la composizione dipende solo dalle dimensioni dell'oggetto.
Nel progetto tutte le finestre di dialogo ereditavano dalla classe DialogWindow comune. L'aggiunta di una nuova logica aziendale portava a codice non funzionante in tutte le classi derivate.
Pro:
Contro:
Le funzioni comuni sono state estratte in classi separate (logging, validazione), che vengono iniettate in ogni dialogo tramite composizione.
Pro:
Contro: