PHP OOP: gestire ereditarietà e polimorfismo in modo pratico

by theArchitect
SHARE
PHP OOP: gestire ereditarietà e polimorfismo in modo pratico
© Guida-HTML5.it

Introduzione

Quando si inizia a usare la programmazione orientata agli oggetti in PHP, si tende spesso a concentrarsi su classi, proprietà e metodi. Tuttavia, uno dei vantaggi più potenti dell’OOP è la possibilità di costruire gerarchie di classi riutilizzabili, usando ereditarietà e polimorfismo.

Questi due concetti ti permettono di modellare meglio il dominio applicativo, ridurre la duplicazione del codice e scrivere componenti più facili da estendere. In pratica, puoi creare una classe base con il comportamento comune e poi specializzarla in classi figlie che aggiungono o modificano funzionalità specifiche.

In questo tutorial vedremo un caso realistico: la gestione di diversi metodi di pagamento in un e-commerce. È un esempio utile perché mostra bene quando conviene usare una classe base e quando invece serve una classe specializzata.

Codice completo

<?php

declare(strict_types=1);

/**
 * Classe base astratta per tutti i pagamenti.
 * Contiene dati e comportamento comune.
 */
abstract class PaymentMethod
{
    public function __construct(
        protected string $holderName,
        protected float $amount
    ) {}

    public function getSummary(): string
    {
        return $this->holderName . ´ deve pagare ´ . number_format($this->amount, 2, ´,´, ´.´) . ´ EUR´;
    }

    // Ogni metodo di pagamento deve implementare la sua logica di esecuzione.
    abstract public function pay(): string;
}

/**
 * Pagamento con carta di credito.
 */
class CreditCardPayment extends PaymentMethod
{
    public function __construct(
        string $holderName,
        float $amount,
        private string $maskedCardNumber
    ) {
        parent::__construct($holderName, $amount);
    }

    public function pay(): string
    {
        return "Pagamento con carta autorizzato per {$this->holderName}, carta {$this->maskedCardNumber}. Importo: " .
            number_format($this->amount, 2, ´,´, ´.´) . " EUR";
    }
}

/**
 * Pagamento con PayPal.
 */
class PayPalPayment extends PaymentMethod
{
    public function __construct(
        string $holderName,
        float $amount,
        private string $email
    ) {
        parent::__construct($holderName, $amount);
    }

    public function pay(): string
    {
        return "Pagamento PayPal eseguito per {$this->holderName} ({$this->email}). Importo: " .
            number_format($this->amount, 2, ´,´, ´.´) . " EUR";
    }
}

/**
 * Pagamento tramite bonifico bancario.
 */
class BankTransferPayment extends PaymentMethod
{
    public function __construct(
        string $holderName,
        float $amount,
        private string $iban
    ) {
        parent::__construct($holderName, $amount);
    }

    public function pay(): string
    {
        return "Bonifico avviato per {$this->holderName}, IBAN {$this->iban}. Importo: " .
            number_format($this->amount, 2, ´,´, ´.´) . " EUR";
    }
}

/**
 * Servizio che utilizza il polimorfismo.
 */
class PaymentProcessor
{
    /**
     * Accetta qualsiasi oggetto che estende PaymentMethod.
     */
    public function process(PaymentMethod $paymentMethod): void
    {
        echo $paymentMethod->getSummary() . PHP_EOL;
        echo $paymentMethod->pay() . PHP_EOL;
    }
}

// Esempio d´uso
$payments = [
    new CreditCardPayment(´Mario Rossi´, 120.50, ´**** **** **** 1234´),
    new PayPalPayment(´Lucia Bianchi´, 89.90, ´[email protected]´),
    new BankTransferPayment(´Andrea Verdi´, 240.00, ´IT60X0542811101000000123456´),
];

$processor = new PaymentProcessor();

foreach ($payments as $payment) {
    $processor->process($payment);
    echo str_repeat(´-´, 50) . PHP_EOL;
}

Spiegazione

Il codice mostra un esempio tipico di ereditarietà in PHP. La classe PaymentMethod è la classe base astratta: rappresenta il concetto generale di pagamento, ma non può essere istanziata direttamente. Questo è corretto, perché un “metodo di pagamento” generico non è sufficiente per eseguire un’operazione concreta.

1. La classe base astratta

La classe PaymentMethod contiene:

  • proprietà comuni: nome del titolare e importo;
  • un metodo concreto getSummary(), utile a tutte le varianti;
  • un metodo astratto pay(), che obbliga le classi figlie a definire la propria logica.

Questo approccio riduce la duplicazione: non serve riscrivere ogni volta la gestione dell’importo o del nome del cliente.

2. Le classi figlie specializzate

Le classi CreditCardPayment, PayPalPayment e BankTransferPayment estendono la classe base e aggiungono i dati specifici del loro contesto:

  • la carta mascherata per il pagamento con carta;
  • l’email per PayPal;
  • l’IBAN per il bonifico.

Ogni classe implementa il metodo pay() in modo diverso. Questo è il cuore del polimorfismo: oggetti diversi rispondono allo stesso messaggio, ma con comportamenti specifici.

3. Il processore di pagamenti

La classe PaymentProcessor riceve un parametro tipizzato come PaymentMethod. Questo è molto importante: il processore non deve conoscere il tipo concreto del pagamento, gli basta sapere che esiste un oggetto compatibile con la classe base.

Quando chiamiamo $paymentMethod->pay(), PHP esegue automaticamente il metodo corretto in base all’oggetto reale passato. Questo rende il codice flessibile e aperto all’estensione.

4. Vantaggi pratici

  • meno duplicazione del codice comune;
  • estendibilità: puoi aggiungere un nuovo metodo di pagamento senza modificare il processore;
  • manutenzione più semplice: le responsabilità sono separate;
  • codice più leggibile: ogni classe ha un ruolo preciso.

Best practice

Usare ereditarietà e polimorfismo in PHP è utile, ma va fatto con criterio. Ecco alcune buone pratiche da seguire.

  • Non abusare dell’ereditarietà: usa una classe base solo quando esiste davvero un rapporto “è un tipo di”. Se il legame è debole, valuta composizione o servizi separati.
  • Preferisci classi astratte quando serve una base comune: se vuoi condividere codice e imporre metodi obbligatori, una classe astratta è spesso la scelta migliore.
  • Usa type hint e tipi di ritorno: rendono il codice più sicuro e comprensibile.
  • Evita logiche troppo complesse nella classe base: la classe padre deve offrire supporto, non diventare un “mostro” difficile da mantenere.
  • Non forzare le classi figlie a ereditare comportamenti inutili: se una sottoclasse non ha senso in un certo contesto, probabilmente la gerarchia è sbagliata.
  • Rendi i metodi astratti davvero necessari: un metodo astratto deve rappresentare un comportamento che ogni variante deve implementare in modo proprio.

Un errore comune è costruire gerarchie troppo profonde. In PHP, come in altri linguaggi, una catena di ereditarietà lunga spesso complica il debug e rende difficile capire dove sia definita una certa logica. Meglio una struttura semplice e ben documentata.

Un altro consiglio importante riguarda la visibilità delle proprietà. Nell’esempio abbiamo usato protected per i dati comuni, così le classi figlie possono accedervi senza esporli all’esterno. Questo mantiene un buon equilibrio tra riuso e incapsulamento.

Riepilogo

Ereditarietà e polimorfismo sono due pilastri dell’OOP in PHP. Con una classe base astratta puoi definire il comportamento condiviso, mentre le classi figlie implementano i dettagli specifici. Il risultato è un codice più pulito, modulare e facile da estendere.

Nell’esempio dei pagamenti abbiamo visto come un processore possa lavorare con oggetti diversi senza conoscere la loro implementazione interna. Questo è uno dei vantaggi più concreti del polimorfismo: scrivere codice che dipende da astrazioni, non da dettagli.

Se vuoi progettare applicazioni PHP solide, impara a riconoscere i casi in cui una gerarchia di classi è davvero utile. Quando il modello è corretto, il codice diventa più naturale da leggere e più semplice da evolvere.

Approfondisci con risorse ufficiali

  • Manuale PHP - Classi e oggetti: documentazione ufficiale per la base dell’OOP.
  • Manuale PHP - Classi astratte: utile per approfondire l’uso dei metodi astratti.
  • Manuale PHP - Ereditarietà: per capire come funzionano classi padre e figlie.
  • Manuale PHP - Type declarations: per migliorare robustezza e chiarezza del codice.
  • Manuale PHP - Promoted properties: comodo per semplificare i costruttori nelle classi moderne.

SHARE