Introduzione
L’ereditarietà e il polimorfismo sono due concetti fondamentali della programmazione orientata agli oggetti. In JavaScript, questi concetti possono essere applicati in modo molto naturale grazie al sistema basato su prototipi e, più recentemente, alla sintassi class introdotta con ES6.
In questo tutorial vedremo un esempio pratico e originale: un piccolo sistema per gestire contenuti multimediali, come video, audio e immagini. L’obiettivo è mostrare come creare una classe base comune e poi specializzarla in sottoclassi con comportamenti diversi, sfruttando il polimorfismo per trattare oggetti differenti in modo uniforme.
Questo scenario è utile in molti contesti reali: una piattaforma e-learning, un CMS, una libreria digitale o un’app che organizza contenuti per tipologia. Il vantaggio principale è avere codice più pulito, riutilizzabile e facile da estendere.
Codice completo
class ContenutoMultimediale {
constructor(titolo, autore) {
this.titolo = titolo;
this.autore = autore;
}
// Metodo base: verrà specializzato nelle sottoclassi
descrivi() {
return `${this.titolo} di ${this.autore}`;
}
// Metodo polimorfico: ogni sottoclasse lo sovrascrive
riproduci() {
return `Riproduzione non supportata per questo contenuto.`;
}
}
class Video extends ContenutoMultimediale {
constructor(titolo, autore, durata, risoluzione) {
super(titolo, autore);
this.durata = durata;
this.risoluzione = risoluzione;
}
descrivi() {
return `${super.descrivi()} - Video ${this.durata} min, ${this.risoluzione}`;
}
riproduci() {
return `Avvio video: "${this.titolo}" in ${this.risoluzione}`;
}
}
class Audio extends ContenutoMultimediale {
constructor(titolo, autore, durata, formato) {
super(titolo, autore);
this.durata = durata;
this.formato = formato;
}
descrivi() {
return `${super.descrivi()} - Audio ${this.durata} min, formato ${this.formato}`;
}
riproduci() {
return `Avvio audio: "${this.titolo}" in formato ${this.formato}`;
}
}
class Immagine extends ContenutoMultimediale {
constructor(titolo, autore, larghezza, altezza) {
super(titolo, autore);
this.larghezza = larghezza;
this.altezza = altezza;
}
descrivi() {
return `${super.descrivi()} - Immagine ${this.larghezza}x${this.altezza}px`;
}
riproduci() {
return `Apertura immagine: "${this.titolo}" (${this.larghezza}x${this.altezza}px)`;
}
}
// Funzione che sfrutta il polimorfismo
function mostraContenuti(contenuti) {
contenuti.forEach((contenuto) => {
console.log(contenuto.descrivi());
console.log(contenuto.riproduci());
console.log("---");
});
}
// Creazione degli oggetti
const libreria = [
new Video("Corso JavaScript Base", "Luca Rossi", 45, "1080p"),
new Audio("Podcast Frontend", "Giulia Bianchi", 30, "mp3"),
new Immagine("Copertina Corso", "Studio Grafico", 1920, 1080)
];
// Uso del sistema
mostraContenuti(libreria); Spiegazione
La classe ContenutoMultimediale rappresenta la superclasse, cioè il modello comune a tutti i contenuti. Contiene proprietà condivise come titolo e autore, oltre a due metodi:
- descrivi(): restituisce una descrizione generica del contenuto.
- riproduci(): è pensato come metodo da specializzare nelle sottoclassi.
Le classi Video, Audio e Immagine estendono la classe base con extends. Questo significa che ereditano proprietà e metodi della superclasse, ma possono aggiungere nuove proprietà specifiche e ridefinire i metodi esistenti.
1. Ereditarietà
Quando una sottoclasse chiama super(...) nel costruttore, sta invocando il costruttore della classe padre. È un passaggio obbligatorio per inizializzare correttamente le proprietà ereditate.
Nel nostro esempio, ogni contenuto multimediale ha titolo e autore, ma ogni tipo aggiunge dati propri:
- Video: durata e risoluzione
- Audio: durata e formato
- Immagine: larghezza e altezza
2. Polimorfismo
Il polimorfismo permette di usare un’interfaccia comune per oggetti diversi. Qui la funzione mostraContenuti() riceve un array di elementi eterogenei, ma non ha bisogno di sapere se sta lavorando con un video, un audio o un’immagine.
Questo è possibile perché ogni oggetto implementa i metodi descrivi() e riproduci() in modo coerente. La funzione li chiama semplicemente, e sarà JavaScript a eseguire la versione corretta in base al tipo reale dell’oggetto.
3. Sovrascrittura dei metodi
Ogni sottoclasse ridefinisce riproduci(). Nella classe base il metodo restituisce un messaggio generico, utile come fallback. Nelle sottoclassi, invece, il metodo viene specializzato per offrire un comportamento più preciso.
Lo stesso vale per descrivi(): la sottoclasse richiama super.descrivi() per riutilizzare la parte comune e aggiunge informazioni specifiche. Questo è un ottimo esempio di riuso del codice senza duplicazione.
4. Perché questo approccio è utile
Immagina di dover aggiungere in futuro una nuova tipologia, ad esempio DocumentoPDF. Con questa struttura ti basta creare una nuova classe che estende ContenutoMultimediale e implementa i metodi richiesti. Non devi modificare la logica della funzione mostraContenuti(), che continuerà a funzionare senza cambiamenti.
Best practice
- Definisci una classe base chiara: la superclasse deve rappresentare solo ciò che è davvero comune a tutte le sottoclassi.
- Evita gerarchie troppo profonde: in JavaScript, strutture di ereditarietà molto complesse diventano difficili da mantenere.
- Usa il polimorfismo per semplificare il codice: scrivi funzioni che lavorano su un’interfaccia comune, non su tipi specifici.
- Sovrascrivi solo i metodi necessari: non duplicare logica identica nelle sottoclassi se puoi riutilizzare super.
- Valida i dati nel costruttore: in un progetto reale, controlla che durata, risoluzione e dimensioni abbiano valori coerenti.
- Usa nomi espliciti: classi e metodi devono comunicare chiaramente il loro ruolo.
Riepilogo
In questo tutorial abbiamo costruito un piccolo sistema di gestione di contenuti multimediali per capire meglio ereditarietà e polimorfismo in JavaScript.
- L’ereditarietà ci permette di creare classi specializzate a partire da una classe base.
- Il polimorfismo ci consente di trattare oggetti diversi tramite una stessa interfaccia.
- La sovrascrittura dei metodi rende il comportamento più specifico e flessibile.
- La funzione che gestisce l’array di contenuti funziona senza conoscere i dettagli interni di ogni classe.
Questo approccio è particolarmente utile quando il progetto deve crescere nel tempo. Aggiungere nuove tipologie di contenuto diventa semplice, perché la struttura è già pensata per l’estensione.
Approfondisci con risorse ufficiali
- MDN Web Docs - Classes: documentazione completa sulle classi in JavaScript.
- MDN Web Docs - Inheritance and the prototype chain: per capire meglio il modello prototipale di JavaScript.
- MDN Web Docs - extends: riferimento sull’ereditarietà tra classi.
- MDN Web Docs - super: guida all’uso del costruttore e dei metodi della classe padre.
- ECMAScript Language Specification: la specifica ufficiale del linguaggio per chi vuole approfondire a livello avanzato.
