Prinicipi di sviluppo SOLID in WinterCMS - L

Luca Benati

In questo articolo vediamo il terzo principio SOLID; Liskov Substitution principle (principio di sostituzione di Liskov)


Pubblicato da Luca Benati il 21 novembre 2022

Eccoci al terzo articolo di questa serie sui principi di sviluppo SOLID, in questo articolo vedremo per cosa sta e cosa dice il terzo principio definito con la "L" di Liskov Substitution principle

Liskov Substitution principle

If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program.

Questo principio afferma che gli oggetti della superclasse devono essere sostituibili con oggetti delle sue classi figlie senza alterare la correttezza del programma e rompere l'applicazione.

Il classico esempio utilizzato per spiegare questo principio è il caso di una classe Quadrato figlia della classe Rettangolo che nel mondo pratico sembrerebbe anche corretto come ragionamento dato che il quadrato è solo un caso di rettangolo con i lati uguali, e qui si nasconde l'insidia perchè a livello applicativo la logica non rimane uguale.

Classe rettangolo


class Rettangolo
{
    public $base;
    public $altezza;

    public function impostaBase(int $base): void { 
        $this->base = $base;
    }

    public function impostaAltezza(int $altezza): void {
        $this->altezza = $altezza;
    }
}

Classe Quadrato derivata dalla classe Rettangolo


class Quadrato extends Rettangolo
{
    public function impostaBase(int $base): void { 
        $this->base = $base;
        $this->altezza = $base;
    }

    public function impostaAltezza(int $altezza): void {
        $this->altezza = $altezza;
        $this->base = $altezza;
    }
}

Come vedi per la classe Quadrato abbiamo dovuto sovrascrivere i metodi impostaBase e impostaAltezza della classe genitore Rettangolo che però cambiano anche il comportamento della classe e di conseguenza violiamo il principio di Liskov in questione in quanto noi non possiamo rimpiazzare l'uso della classe Rettangolo con la classe Quadrato.

Quanto appena detto lo si può dimostrare con un semplice Unit Test di questo tipo


public function testCalcolaArea()
{
    $forma = new Rettangolo();
    $forma->impostaBase(10);
    $forma->impostaAltezza(2);

    $this->assertEquals($forma->calcolaArea(), 20);

    $forma->impostaBase(5);
    $this->assertEquals($forma->calcolaArea(), 10);
}

Se in questo test sostituissimo la classe Rettangolo con quella Quadrato il test non passerebbe (4!=20), questo è un esempio di come cambiare la logica di funzionamento dei metodi impostaBase e impostaAltezza viola il principio di Liskov rompendo l'applicazione. Non possiamo modificare come funzionano i metodi della classe genitore.

Da questo si evince che se vogliamo proprio sovrascrivere i metodi della superclasse dobbiamo garantire alcuni requisiti affinchè rispetti il principio:

  1. i metodi pubblici sovrascritti dalla sottoclasse, devono garantire che la logica applicativa sia rispettata.
  2. le regole di validazione nei metodi sovrascritti nella sottoclasse devono essere ugualmente o meno stringenti di quelli della superclasse, altrimenti si rischia di far lanciare eccezioni dopo la sostituzione.
  3. in modo analogo al punto precedente, i valori ritornati dai metodi sovrascritti devono rispettare le stesse regole o avere regole più ristrette di quelle della superclasse
  4. non devono essere introdotte nuove eccezioni non presenti nella superclasse.

Ad ogni modo se è necessario sovrascrivere i metodi della classe genitore è più facile che non sia corretto l'uso dell'ereditarietà che si sta facendo nel caso specifico e sarebbe probabilemnte meglio cambiare approccio derivando da una classe diversa o implementando interfaccie adeguate.

Anche per quanto riguarda la "L" è tutto, nel prossimo articolo vedremo il quarto principio detto "Interface segregation".

P.S. Io uso nomi di variabili, classi e metodi in italiano perchè sia più semplice per chi è più nuovo a capire le informazioni spiegate ma mi raccomando scrivete sempre tutto (variabili, funzioni, metodi, interfaccie, classi ecc ecc) sempre in inglese e seguendo le buone pratiche di nomenclatura, se ancora non l'hai letto ti consiglio il libro Clean Code di Robert C. Martin edito dalla Apogeo

Happy coding!


Lunga vita e prosperità

Ti interessa un argomento non trattato?