Introduzione
L’ereditarietà e il polimorfismo sono due concetti fondamentali della programmazione orientata agli oggetti. In JavaScript permettono di creare una struttura di classi riutilizzabile, nella quale una classe generale definisce comportamenti comuni e le classi specializzate li estendono o li modificano.
In questo tutorial costruiremo un sistema per elaborare diversi tipi di report aziendali. Immaginiamo di avere report sulle vendite, report sulle attività tecniche e report finanziari. Tutti i report condividono alcune caratteristiche, come un titolo, una data e un metodo per visualizzare un riepilogo. Tuttavia, ogni tipo di report deve produrre informazioni differenti.
Questo è un caso perfetto per il polimorfismo: il programma può chiamare lo stesso metodo, per esempio generaRiepilogo(), su oggetti diversi, lasciando a ogni classe il compito di fornire la propria implementazione.
Codice completo
class Report {
constructor(titolo, dataCreazione) {
if (new.target === Report) {
throw new Error(
"Report è una classe astratta e non può essere istanziata direttamente."
);
}
this.titolo = titolo;
this.dataCreazione = dataCreazione;
}
// Metodo comune a tutti i report
intestazione() {
return `${this.titolo} - creato il ${this.dataCreazione}`;
}
// Metodo da implementare nelle classi figlie
generaRiepilogo() {
throw new Error("Il metodo generaRiepilogo() deve essere implementato.");
}
// Metodo comune che utilizza il comportamento polimorfico
stampa() {
return `${this.intestazione()}n${this.generaRiepilogo()}`;
}
}
class ReportVendite extends Report {
constructor(titolo, dataCreazione, vendite) {
super(titolo, dataCreazione);
this.vendite = vendite;
}
generaRiepilogo() {
const totale = this.vendite.reduce(
(somma, vendita) => somma + vendita.importo,
0
);
const numeroOrdini = this.vendite.length;
return [
`Tipo: Report vendite`,
`Ordini elaborati: ${numeroOrdini}`,
`Totale vendite: ${totale.toFixed(2)} euro`
].join("n");
}
}
class ReportTecnico extends Report {
constructor(titolo, dataCreazione, interventi) {
super(titolo, dataCreazione);
this.interventi = interventi;
}
generaRiepilogo() {
const completati = this.interventi.filter(
intervento => intervento.stato === "completato"
).length;
const aperti = this.interventi.filter(
intervento => intervento.stato === "aperto"
).length;
return [
`Tipo: Report tecnico`,
`Interventi completati: ${completati}`,
`Interventi ancora aperti: ${aperti}`
].join("n");
}
}
class ReportFinanziario extends Report {
constructor(titolo, dataCreazione, entrate, uscite) {
super(titolo, dataCreazione);
this.entrate = entrate;
this.uscite = uscite;
}
generaRiepilogo() {
const saldo = this.entrate - this.uscite;
const risultato = saldo >= 0 ? "positivo" : "negativo";
return [
`Tipo: Report finanziario`,
`Entrate: ${this.entrate.toFixed(2)} euro`,
`Uscite: ${this.uscite.toFixed(2)} euro`,
`Saldo ${risultato}: ${saldo.toFixed(2)} euro`
].join("n");
}
}
// Funzione polimorfica: non conosce i dettagli delle classi concrete
function stampaTuttiIReport(report) {
console.log(report.stampa());
console.log("------------------------------");
}
const reportVendite = new ReportVendite(
"Vendite del mese",
"15/10/2026",
[
{ cliente: "Azienda Alfa", importo: 1250 },
{ cliente: "Azienda Beta", importo: 850.5 },
{ cliente: "Azienda Gamma", importo: 430 }
]
);
const reportTecnico = new ReportTecnico(
"Attività del reparto assistenza",
"15/10/2026",
[
{ descrizione: "Aggiornamento server", stato: "completato" },
{ descrizione: "Configurazione backup", stato: "aperto" },
{ descrizione: "Controllo rete", stato: "completato" }
]
);
const reportFinanziario = new ReportFinanziario(
"Situazione finanziaria",
"15/10/2026",
12000,
8450.75
);
// Tutti gli oggetti vengono trattati allo stesso modo
const report = [
reportVendite,
reportTecnico,
reportFinanziario
];
report.forEach(stampaTuttiIReport); Spiegazione
La classe base
Report è la classe base della gerarchia. Contiene le proprietà comuni a tutti i report, cioè titolo e dataCreazione. Il metodo intestazione() è anch’esso condiviso e non deve essere riscritto dalle classi figlie.
Nel costruttore viene utilizzato new.target. Questa proprietà permette di verificare quale classe è stata usata direttamente con new. Se qualcuno prova a eseguire new Report(), viene generato un errore. In questo modo simuliamo una classe astratta, cioè una classe pensata solo per essere estesa.
Il metodo generaRiepilogo() solleva un errore perché la classe base non conosce il modo corretto di descrivere un report specifico. Saranno le sottoclassi a implementare questo comportamento.
Le classi derivate
Le classi ReportVendite, ReportTecnico e ReportFinanziario usano la sintassi extends per ereditare da Report. Nei rispettivi costruttori chiamano super(), che esegue il costruttore della classe padre.
Ogni sottoclasse ridefinisce generaRiepilogo() secondo le proprie esigenze. Il report delle vendite somma gli importi degli ordini, quello tecnico conta gli interventi in base allo stato e quello finanziario calcola il saldo tra entrate e uscite.
Il polimorfismo in azione
La funzione stampaTuttiIReport() riceve un generico oggetto report. Non controlla se l’oggetto sia un report di vendita, tecnico o finanziario. Chiama semplicemente report.stampa().
Il metodo stampa(), ereditato dalla classe base, invoca a sua volta this.generaRiepilogo(). A seconda dell’oggetto concreto, JavaScript esegue il metodo definito nella sottoclasse corretta. Questo comportamento è il polimorfismo: la stessa chiamata produce risultati diversi in base al tipo reale dell’oggetto.
Il vantaggio è evidente: per aggiungere un nuovo tipo di report non dobbiamo modificare la funzione stampaTuttiIReport(). È sufficiente creare una nuova classe che estenda Report e implementi generaRiepilogo().
Best practice
- Mantieni la classe base focalizzata: inserisci nella classe padre solo proprietà e comportamenti davvero comuni.
- Usa metodi con responsabilità chiare:
generaRiepilogo()si occupa del riepilogo, mentrestampa()coordina la presentazione. - Valida i dati in ingresso: in un’applicazione reale dovresti controllare che gli importi siano numeri e che gli array contengano oggetti validi.
- Evita controlli basati sul tipo: non è necessario usare molti
ifcomeinstanceof. Il polimorfismo consente di delegare il comportamento all’oggetto corretto. - Preferisci metodi comuni e contratti chiari: tutte le sottoclassi devono rispettare l’interfaccia prevista dalla classe base.
- Considera la composizione quando serve: se le classi diventano troppo complesse, può essere meglio separare il calcolo dei dati dalla formattazione del report.
Riepilogo
L’ereditarietà permette alle classi specializzate di riutilizzare proprietà e metodi definiti in una classe generale. In JavaScript si realizza principalmente con extends e super().
Il polimorfismo permette di usare oggetti diversi attraverso la stessa interfaccia. Nel nostro esempio, tutti i report espongono il metodo stampa(), ma ciascuno produce un riepilogo differente tramite la propria implementazione di generaRiepilogo().
Questa tecnica rende il codice più estendibile: possiamo aggiungere nuovi report senza modificare il codice che li utilizza. È un esempio concreto del principio “aperto all’estensione, chiuso alla modifica”, utile nei progetti JavaScript di dimensioni medio-grandi.
