Le classi figlie non dovrebbero mai influire o modificare le definizioni del tipo della classe genitore.

Tempo di lettura stimato: 4 minuti

Il concetto di questo principio è 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:

Principio

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 noto 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/

Articoli correlati

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 di LSP è una violazione latente di OCP” (Robert C. Martin), e il Template Method Design Pattern è un classico esempio di rispetto e implementazione di LSP, che a sua volta è una delle soluzioni per rispettare anche OCP .

Newsletter sull’Innovazione
Non perderti le notizie più importanti sull'Innovazione. Iscriviti per riceverle via e-mail.

Esempio di violazione LSP

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 a LSP, 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.

Ercole Palmeri

Newsletter sull’Innovazione
Non perderti le notizie più importanti sull'Innovazione. Iscriviti per riceverle via e-mail.