principio di sostituzione di liskov, terzo principio S.O.L.I.D.
Le classi figlie non dovrebbero mai influire o modificare le definizioni del tipo della classe genitore.
Questo è il principio di sostituzione di liskov, e in questo articolo andremo a vedere l’applicazione anche con degli esempi.
4 minuti
Il concetto del principio di sostituzione di liskov, è stato introdotto da Barbara Liskov in un keynote della conferenza del 1987 e successivamente pubblicato in un articolo insieme a Jannette Wing nel 1994.
La loro definizione originale è la seguente: Le classi figlie non dovrebbero mai influire o modificare le definizioni del tipo della classe genitore.
Principio di sostituzione di liskov
Sia q (x) una proprietà dimostrabile su oggetti x di tipo T. Allora q (y) dovrebbe essere dimostrabile per oggetti y di tipo S dove S è un sottotipo di T.
Successivamente, con la pubblicazione dei principi SOLID di Robert C.Martin nel suo libro Agile Software Development, Principles, Patterns, and Practices e poi ripubblicata nella versione C # del libro Agile Principles, Patterns, and Practices in C #, la definizione divenne nota come principio di sostituzione di Liskov.
Questo ci porta alla definizione data da Robert C. Martin: I sottotipi devono essere sostituibili con i loro tipi di base.
Più semplicemente, una sottoclasse dovrebbe sovrascrivere i metodi della classe genitore in un modo che non interrompe la funzionalità dal punto di vista di un cliente. Ecco un semplice esempio per dimostrare il concetto.
class Vehicle {
function startEngine() {
// Default engine start functionality
}
function accelerate() {
// Default acceleration functionality
}
}
Data una classe Veicolo – può essere astratta – e due implementazioni:
class Car extends Vehicle {
function startEngine() {
$this->engageIgnition();
parent::startEngine();
}
private function engageIgnition() {
// Ignition procedure
}
}
class ElectricBus extends Vehicle {
function accelerate() {
$this->increaseVoltage();
$this->connectIndividualEngines();
}
private function increaseVoltage() {
// Electric logic
}
private function connectIndividualEngines() {
// Connection logic
}
}
class Driver {
function go(Vehicle $v) {
$v->startEngine();
$v->accelerate();
}
}
Una classe client dovrebbe essere in grado di utilizzare entrambi, se può utilizzare Vehicle.
Il che ci porta a una semplice implementazione del Template Method Design Pattern come l’abbiamo utilizzato nel OCP.
Potrebbe interessarti anche il secondo principio SOLID: https://bloginnovazione.it/open-closed-secondo-principio-s-o-l-i-d/3906/
Sulla base della nostra precedente esperienza con il principio Open / Closed, possiamo concludere che il principio di sostituzione di Liskov è in stretta relazione con l’OCP. Infatti, “una violazione del principio di violazione di liskov è una violazione latente di OCP” (Robert C. Martin), e il Template Method Design Pattern è un classico esempio di rispetto e implementazione del principio di sostituzione di liskov, che a sua volta è una delle soluzioni per rispettare anche OCP .
Esempio di violazione del principio sostituzione liskov
class Rectangle {
private $topLeft;
private $width;
private $height;
public function setHeight($height) {
$this->height = $height;
}
public function getHeight() {
return $this->height;
}
public function setWidth($width) {
$this->width = $width;
}
public function getWidth() {
return $this->width;
}
}
Iniziamo con una forma geometrica di base, un rettangolo. È solo un semplice oggetto dati con setter e getter per larghezza e altezza. Immagina che la nostra applicazione funzioni e sia già distribuita su diversi client. Ora hanno bisogno di una nuova funzionalità. Devono essere in grado di manipolare i quadrati.
Nella vita reale, in geometria, un quadrato è una particolare forma di rettangolo. Quindi potremmo provare a implementare una classe Square che estende una classe Rectangle. Si dice spesso che una classe figlia è una classe genitore, e anche questa espressione è conforme al principio sostituzione liskov, almeno a prima vista.
class Square extends Rectangle {
public function setHeight($value) {
$this->width = $value;
$this->height = $value;
}
public function setWidth($value) {
$this->width = $value;
$this->height = $value;
}
}
Un quadrato è un rettangolo con larghezza e altezza uguali e potremmo eseguire una strana implementazione come nell’esempio precedente. Potremmo sovrascrivere entrambi i setter per impostare sia l’altezza che la larghezza. Ma in che modo ciò influirà sul codice client?
class Client {
function areaVerifier(Rectangle $r) {
$r->setWidth(5);
$r->setHeight(4);
if($r->area() != 20) {
throw new Exception('Bad area!');
}
return true;
}
}
È concepibile avere una classe client che verifica l’area del rettangolo e genera un’eccezione se è sbagliato.
function area() {
return $this->width * $this->height;
}
Ovviamente abbiamo aggiunto il metodo sopra alla nostra classe Rectangle per fornire l’area.
class LspTest extends PHPUnit_Framework_TestCase {
function testRectangleArea() {
$r = new Rectangle();
$c = new Client();
$this->assertTrue($c->areaVerifier($r));
}
}
E abbiamo creato un semplice test inviando un oggetto rettangolo vuoto al verificatore di area e il test viene superato. Se la nostra classe Square è definita correttamente, inviarla all’areaVerifier () del Cliente non dovrebbe interrompere la sua funzionalità. Dopo tutto, un quadrato è un rettangolo in tutto il senso matematico. Ma è la nostra classe?
function testSquareArea() {
$r = new Square();
$c = new Client();
$this->assertTrue($c->areaVerifier($r));
}
Quindi, la nostra classe Square non è dopotutto un Rettangolo. Infrange le leggi della geometria. Fallisce e viola il principio di sostituzione di Liskov.