Skip to content

Introduzione al Polimorfismo

“One day Trurl the constructor put together a machine that could create anything starting with n.” — Stanislaw Lem, The Cyberiad

Il Polimorfismo (dal greco “molte forme”) consiste nello scrivere codice una volta sola e poterlo riutilizzare con tipi di dato diversi. Questo permette di creare programmi altamente modulari (loosely coupled), evitando copiature ripetitive di codice e facilitando la manutenzione.

In C++ esistono due macro-categorie:

  1. Polimorfismo a Compile-Time (Tempo di Compilazione): tramite i Template, dove il compilatore conosce a priori tutti i tipi in gioco (ne parleremo nel Capitolo 6).
  2. Polimorfismo a Runtime (Tempo di Esecuzione): tramite Ereditarietà e Interfacce, dove il tipo esatto dell’oggetto viene determinato dinamicamente mentre il programma sta girando.

Immagina di programmare il sistema centrale di una Banca. La classe `Bank` effettua trasferimenti di denaro, ma deve obbligatoriamente tenere traccia di tutte le transazioni tramite un Logger (registratore).

Inizialmente, creiamo un semplice `ConsoleLogger`:

struct ConsoleLogger {
void log_transfer(long from, long to, double amount) {
printf("[cons] %ld -> %ld: %f\\n", from, to, amount);
}
};

Il problema emerge quando il nostro manager ci chiede di supportare anche un `FileLogger`, un `NetworkLogger` e così via, e di poterli scambiare mentre il programma gira (ad esempio se la rete cade, passiamo al file system).

Una prima, debole soluzione potrebbe essere l’uso di un `enum` interno alla Banca e di un enorme costrutto `switch` per decidere chi chiamare:

Sebbene funzioni, presenta disastrosi problemi di design:

  1. Devi scrivere una nuova classe logger.
  2. Devi aggiungere un nuovo valore all’`enum`.
  3. Devi aggiungere un nuovo `case` allo `switch`.
  4. Devi istanziare tutti i possibili logger come variabili membro dentro `Bank`!

È un sacco di lavoro per una modifica banale. Quello che vorremmo veramente è che `Bank` possieda un singolo puntatore generico a un Logger, e che a prescindere da quale logger effettivo stiamo usando, chiami la funzione `log_transfer`. La Banca non deve sapere come il logger funziona, le basta sapere come chiamarlo.

Questo è il concetto di Interfaccia (Interface).