Introduzione
Quando si parla di ereditarietà e polimorfismo in JavaScript, spesso si pensa subito a esempi astratti o a gerarchie di oggetti troppo teoriche. In realtà, questi concetti diventano molto più chiari se li applichiamo a un caso concreto e utile: la gestione di forme geometriche in un’applicazione.
Immagina di dover creare un sistema che calcola area e perimetro di figure diverse come cerchi, rettangoli e triangoli. Tutte le forme condividono alcune caratteristiche comuni, ma ognuna ha regole specifiche per i propri calcoli. Qui entrano in gioco ereditarietà e polimorfismo:
- Ereditarietà: una classe base definisce ciò che le forme hanno in comune.
- Polimorfismo: ogni forma può implementare lo stesso metodo in modo diverso.
Questo approccio è molto usato nello sviluppo reale perché rende il codice più organizzato, estendibile e facile da mantenere.
Codice completo
class Shape {
constructor(name) {
this.name = name;
}
// Metodo comune: ogni forma deve poter descrivere sé stessa
describe() {
return `Sono una forma geometrica: ${this.name}`;
}
// Metodo "astratto" simulato: le sottoclassi devono sovrascriverlo
area() {
throw new Error("Il metodo area() deve essere implementato nelle sottoclassi");
}
// Altro metodo "astratto" simulato
perimeter() {
throw new Error("Il metodo perimeter() deve essere implementato nelle sottoclassi");
}
}
class Circle extends Shape {
constructor(radius) {
super("Cerchio");
this.radius = radius;
}
area() {
return Math.PI * this.radius * this.radius;
}
perimeter() {
return 2 * Math.PI * this.radius;
}
}
class Rectangle extends Shape {
constructor(width, height) {
super("Rettangolo");
this.width = width;
this.height = height;
}
area() {
return this.width * this.height;
}
perimeter() {
return 2 * (this.width + this.height);
}
}
class Triangle extends Shape {
constructor(base, height, sideA, sideB, sideC) {
super("Triangolo");
this.base = base;
this.height = height;
this.sideA = sideA;
this.sideB = sideB;
this.sideC = sideC;
}
area() {
return (this.base * this.height) / 2;
}
perimeter() {
return this.sideA + this.sideB + this.sideC;
}
}
// Funzione polimorfica: lavora con qualsiasi Shape
function printShapeInfo(shape) {
console.log(shape.describe());
console.log(`Area: ${shape.area().toFixed(2)}`);
console.log(`Perimetro: ${shape.perimeter().toFixed(2)}`);
console.log("----------------------------");
}
// Creazione di oggetti diversi
const circle = new Circle(5);
const rectangle = new Rectangle(4, 6);
const triangle = new Triangle(8, 5, 5, 6, 7);
// Stesso codice, comportamenti diversi
printShapeInfo(circle);
printShapeInfo(rectangle);
printShapeInfo(triangle); Spiegazione
Nel codice sopra abbiamo creato una gerarchia semplice ma molto efficace. La classe Shape è la classe base: contiene il comportamento comune a tutte le forme e definisce due metodi che le sottoclassi devono implementare.
1. La classe base
Shape non rappresenta una forma specifica, ma un concetto generale. Per questo motivo i metodi area() e perimeter() non hanno un’implementazione reale. Invece, lanciano un errore se qualcuno prova a usarli direttamente. Questa tecnica è utile per simulare un comportamento “astratto” in JavaScript.
2. Le sottoclassi
Le classi Circle, Rectangle e Triangle estendono Shape con la parola chiave extends. Ognuna chiama super() per inizializzare la parte comune ereditata dalla classe base.
- Circle calcola area e perimetro usando il raggio.
- Rectangle usa larghezza e altezza.
- Triangle calcola l’area con base e altezza, e il perimetro sommando i lati.
3. Il polimorfismo
La funzione printShapeInfo() è il cuore del polimorfismo. Riceve un oggetto generico shape e chiama i metodi describe(), area() e perimeter(). Non le importa se la forma è un cerchio, un rettangolo o un triangolo: sa solo che quei metodi esistono.
Questo è il vantaggio principale del polimorfismo: scrivi una logica unica che funziona con oggetti diversi, purché rispettino la stessa interfaccia comportamentale.
4. Perché è utile nel mondo reale
Immagina un software per disegno tecnico, un editor grafico o un sistema CAD semplificato. Potresti avere decine di forme diverse, ma tutte devono essere:
- renderizzate a schermo
- descritte in una sidebar
- misurate in termini di area e perimetro
Con ereditarietà e polimorfismo puoi creare una base comune e aggiungere nuove forme senza riscrivere la logica principale.
Best practice
- Usa l’ereditarietà solo quando esiste davvero una relazione “è un”: un cerchio è una forma, un rettangolo è una forma. Se la relazione non è naturale, meglio preferire composizione o funzioni.
- Definisci una responsabilità chiara per la classe base: la classe
Shapenon deve conoscere i dettagli delle forme concrete. - Evita gerarchie troppo profonde: più livelli di ereditarietà rendono il codice difficile da leggere e mantenere.
- Fai attenzione ai metodi “astratti” simulati: in JavaScript non esistono classi astratte native come in altri linguaggi, quindi lanciare un errore è una soluzione pratica ma va usata con criterio.
- Progetta interfacce coerenti: se una funzione usa oggetti polimorfici, assicurati che tutti espongano gli stessi metodi con lo stesso significato.
- Valida i dati in ingresso: ad esempio, un raggio negativo o un lato nullo non hanno senso in geometria. In un progetto reale dovresti controllare questi valori nel costruttore.
Riepilogo
In questo tutorial abbiamo visto come usare ereditarietà e polimorfismo in JavaScript attraverso un esempio concreto: la gestione di forme geometriche. La classe base Shape fornisce una struttura comune, mentre le classi derivate implementano i dettagli specifici di ciascuna forma.
Il polimorfismo ci ha permesso di scrivere una sola funzione, printShapeInfo(), capace di lavorare con oggetti diversi senza conoscere in anticipo il loro tipo preciso. Questo rende il codice più flessibile, pulito e facilmente estendibile.
Se domani volessi aggiungere una nuova forma, come un pentagono o un esagono, ti basterebbe creare una nuova classe che estende Shape e implementa i metodi richiesti. Nessuna modifica alla funzione polimorfica sarebbe necessaria.
Approfondisci con risorse ufficiali
- MDN Web Docs - Classi JavaScript: documentazione completa sulle classi ES6 e la sintassi
extendsesuper. - MDN Web Docs - Ereditarietà in JavaScript: approfondimento sul modello a prototipi e sulle catene di ereditarietà.
- MDN Web Docs - Object-oriented JavaScript: panoramica sulla programmazione orientata agli oggetti nel linguaggio JavaScript.
- ECMAScript Language Specification: riferimento ufficiale per chi vuole studiare il comportamento del linguaggio in modo rigoroso.
