Esplorazione del principio di sostituzione di Liskov

Il termine SOLID è un acronimo popolare utilizzato per fare riferimento a una serie di cinque principi di architettura software. Questi includono: SRP (Single Responsibility), Open / Close, Liskov's Substitution, Interface Segregation e Dependency Inversion.

LSP (Liskov Substitution Principle) è un principio fondamentale dell'OOP e afferma che le classi derivate dovrebbero essere in grado di estendere le loro classi base senza modificare il loro comportamento. In altre parole, le classi derivate dovrebbero essere sostituibili con i loro tipi di base, cioè, un riferimento a una classe base dovrebbe essere sostituibile con una classe derivata senza influenzare il comportamento. Il principio di sostituzione di Liskov rappresenta un forte sottotipo comportamentale ed è stato introdotto da Barbara Liskov nell'anno 1987.

Secondo Barbara Liskov, "Ciò che si vuole qui è qualcosa di simile alla seguente proprietà di sostituzione: Se per ogni oggetto o1 di tipo S c'è un oggetto o2 di tipo T tale che per tutti i programmi P definiti in termini di T, il comportamento di P è invariato quando o1 è sostituito da o2, allora S è un sottotipo di T. "

Un classico esempio di violazione del principio di sostituzione di Liskov è il problema Rettangolo - Quadrato. La classe Square estende la classe Rectangle e presuppone che larghezza e altezza siano uguali.

Considera la seguente classe. La classe Rectangle contiene due membri dati: larghezza e altezza. Ci sono anche tre proprietà: Altezza, Larghezza e Area. Mentre le prime due proprietà impostano l'altezza e la larghezza del rettangolo, la proprietà Area ha un getter che restituisce l'area del rettangolo.

 class Rectangle

    {

        protected int width;

        protected int height;

         public virtual int Width

        {

            get

            {

                return width;

            }

            set

            {

                width = value;

            }

        }

 

        public virtual int Height

        {

            get

            {

                return height;

            }

            set

            {

                height = value;

            }

        }

               

       public int Area

        {

            get

            {

                return height * width;

            }

         }    

    }

Un quadrato è un tipo di rettangolo i cui lati hanno tutte le stesse dimensioni, ovvero la larghezza e l'altezza di un quadrato sono le stesse.

class Square : Rectangle

    {

        public override int Width

        {

            get

            {

                return width;

            }

            set

            {

                width = value;

                height = value;

            }

        }

        public override int Height

        {

            get

            {

                return width;

            }

            set

            {

                width = value;

                height = value;

            }

        }

    }

 Considera un'altra classe chiamata ObjectFactory.

 class ObjectFactory

    {

        public static Rectangle GetRectangleInstance()

        {

            return new Square();

        }

    }

Si noti che i setter per le proprietà Width e Height nella classe Square sono stati sovrascritti e modificati per garantire che l'altezza e la larghezza siano le stesse. Creiamo ora un'istanza della classe Rectangle usando e impostiamo le sue proprietà di altezza e larghezza.

Rectangle s = ObjectFactory.GetRectangleInstance();

s.Height = 9;

s.Width = 8;

Console.WriteLine(s.Area);

Lo snippet di codice sopra, una volta eseguito, visualizzerebbe il valore 64 nella console. Il valore atteso è 72 poiché la larghezza e l'altezza menzionate sono rispettivamente 9 e 8. Questa è una violazione del principio di sostituzione di Liskov. Questo perché la classe Square che ha esteso la classe Rectangle ha modificato il comportamento. Per garantire che il principio di sostituzione di Liskov non venga violato, la classe Square può estendere la classe Rectangle ma non dovrebbe modificare il comportamento. Il comportamento è stato cambiato modificando i setter per entrambe le proprietà Width e Height. I valori di altezza e larghezza sono gli stessi se si tratta di un quadrato: non dovrebbero essere gli stessi se si tratta di un rettangolo.

Come risolviamo questo problema, ovvero assicuriamo che questo principio non venga violato? Bene, puoi introdurre una nuova classe chiamata Quadrilatero e assicurarti che entrambe le classi Rettangolo e Quadrato estendano la classe Quadrilatero.

 public class Quadrilateral

    {

        public virtual int Height { get; set; }

        public virtual int Width { get; set; }

        public int Area

        {

            get

            {

                return Height * Width;

            }

        }

    } 

Ora, entrambe le classi Rectangle e Square dovrebbero estendere la classe Quadrilateral e impostare i valori delle proprietà Width e Height in modo appropriato. In sostanza, le classi derivate dovrebbero avere la funzionalità necessaria per impostare i valori di queste proprietà in base al tipo di istanza quadrilatera per cui è necessario calcolare l'area. Si noti che entrambe le proprietà Height e Width sono state contrassegnate come virtuali nella classe Quadrilateral, il che significa che queste proprietà dovrebbero essere sovrascritte dalle classi che derivano la classe Quadrilateral.

Il principio di sostituzione di Liskov è un'estensione del principio di apertura e chiusura e viene violato quando si scrive codice che genera "eccezioni non implementate" o si nascondono metodi in una classe derivata che sono stati contrassegnati come virtuali nella classe di base. Se il tuo codice aderisce al principio di sostituzione di Liskov, hai molti vantaggi. Questi includono: riutilizzabilità del codice, accoppiamento ridotto e manutenzione più semplice.