Introduzione
L’ereditarietà e il polimorfismo sono due concetti fondamentali della programmazione orientata agli oggetti in PHP. In questo tutorial li vedremo in un contesto molto pratico: la gestione di una gerarchia di forme geometriche, come cerchi, rettangoli e triangoli.
Questo scenario è utile perché mostra bene come una classe base possa definire un comportamento comune, mentre le classi figlie implementano i dettagli specifici. In un’applicazione reale, un approccio del genere può essere usato in motori grafici, software CAD, generazione di report o calcolo di aree e perimetri in modo uniforme.
L’obiettivo è creare un sistema in cui possiamo trattare tutte le forme allo stesso modo, pur mantenendo il comportamento corretto per ciascun tipo. È qui che entra in gioco il polimorfismo: il codice client non deve sapere quale forma sta usando, ma può chiamare gli stessi metodi e ottenere risultati diversi.
Codice completo
<?php
declare(strict_types=1);
/**
* Classe base astratta per tutte le forme.
* Definisce l´interfaccia comune e alcuni comportamenti condivisi.
*/
abstract class Shape
{
public function __construct(
protected string $name
) {}
public function getName(): string
{
return $this- Spiegazione
Il punto di partenza è la classe astratta Shape. Una classe astratta non può essere istanziata direttamente, ma serve come base per altre classi. Qui definisce due metodi astratti: area() e perimeter(). Questo significa che ogni forma concreta è obbligata a implementare questi comportamenti.
1. Ereditarietà: la classe base e le classi figlie
Le classi Circle, Rectangle e Triangle estendono Shape. Con l’ereditarietà, ogni classe figlia riusa la struttura comune della classe base e aggiunge la propria logica specifica.
Per esempio, tutte le forme hanno un nome, ma il calcolo dell’area cambia in base alla geometria. Questo è un caso classico in cui l’ereditarietà riduce la duplicazione di codice e rende il modello più ordinato.
2. Polimorfismo: trattare oggetti diversi allo stesso modo
La funzione printShapeInfo(Shape $shape) è il cuore del polimorfismo. Riceve un parametro di tipo Shape, quindi può accettare qualsiasi oggetto che appartenga a questa gerarchia. Quando chiama $shape->describe(), PHP esegue il metodo della classe concreta reale: cerchio, rettangolo o triangolo.
Questo comportamento è estremamente utile perché il codice esterno non deve conoscere i dettagli di ogni forma. Basta sapere che l’oggetto è una Shape e che supporta i metodi richiesti.
3. Metodo comune con comportamento condiviso
Il metodo describe() è definito nella classe base e usa i metodi astratti area() e perimeter(). È un ottimo esempio di come una classe astratta possa fornire una logica comune, lasciando alle classi figlie il compito di completare i dettagli.
In questo modo evitiamo di riscrivere la stessa formattazione in ogni classe concreta. Se domani volessimo cambiare il formato della descrizione, basterebbe modificare un solo punto.
4. Uso di array di oggetti eterogenei
L’array $shapes contiene oggetti di tipi diversi, ma tutti appartenenti alla stessa gerarchia. Questo è uno dei vantaggi più visibili del polimorfismo: possiamo iterare su una collezione mista e chiamare gli stessi metodi su ogni elemento.
La funzione calculateTotalArea() mostra un altro caso pratico: sommare le aree di tutte le forme senza dover scrivere un blocco if o switch per ogni classe concreta.
Best practice
- Usa classi astratte quando esiste un concetto comune: se tutte le entità condividono proprietà e metodi di base, una classe astratta è spesso la scelta più pulita.
- Definisci contratti chiari: i metodi astratti obbligano le classi figlie a implementare funzionalità essenziali, riducendo il rischio di comportamenti incompleti.
- Evita logiche condizionali troppo grandi: invece di usare molti if o switch per distinguere i tipi, sfrutta il polimorfismo e lascia che sia l’oggetto a decidere cosa fare.
- Usa type hinting: dichiarare i tipi nei parametri e nei return type migliora la leggibilità e aiuta a intercettare errori prima.
- Separare i calcoli dalla presentazione: il metodo describe() è utile per l’output, ma in applicazioni più grandi conviene tenere distinta la logica di dominio dalla formattazione.
- Valida i dati in ingresso: raggi, larghezze e altezze non dovrebbero mai essere negativi. In un progetto reale, aggiungi controlli nel costruttore o in una factory.
- Preferisci nomi espliciti: classi come Circle, Rectangle e Triangle rendono il codice immediatamente comprensibile.
Riepilogo
In questo tutorial abbiamo visto come usare ereditarietà e polimorfismo in PHP per modellare una gerarchia di forme geometriche. La classe astratta Shape definisce il contratto comune, mentre le classi figlie implementano i calcoli specifici di area e perimetro.
Il vantaggio principale di questo approccio è la flessibilità: possiamo aggiungere nuove forme senza modificare il codice che lavora con la classe base. Basta creare una nuova classe che estende Shape e implementa i metodi richiesti.
Questo è il tipo di architettura che rende il codice più pulito, scalabile e facile da mantenere. Quando il dominio dell’applicazione cresce, il polimorfismo permette di evitare strutture rigide e di lavorare con oggetti diversi in modo uniforme.
Approfondisci con risorse ufficiali
- PHP Manual - Classi e oggetti: documentazione ufficiale su ereditarietà, visibilità e metodi delle classi.
- PHP Manual - Classi astratte: approfondimento sul ruolo delle classi astratte nella progettazione orientata agli oggetti.
- PHP Manual - Type declarations: utile per capire come tipizzare parametri, proprietà e valori di ritorno.
- PHP Manual - Late Static Bindings: interessante per scenari più avanzati di ereditarietà.
- PHP Manual - Traits: risorsa utile per confrontare ereditarietà e riuso di codice tramite trait.
