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:
- i metodi pubblici sovrascritti dalla sottoclasse, devono garantire che la logica applicativa sia rispettata.
- 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.
- 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
- 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à