Verkenning van het Liskov-substitutieprincipe

De term SOLID is een populair acroniem dat wordt gebruikt om te verwijzen naar een set van vijf principes van softwarearchitectuur. Deze omvatten: SRP (Single Responsibility), Open / Close, Liskov's Substitution, Interface Segregation en Dependency Inversion.

LSP (Liskov Substitution Principle) is een fundamenteel principe van OOP en stelt dat afgeleide klassen hun basisklassen moeten kunnen uitbreiden zonder hun gedrag te veranderen. Met andere woorden, afgeleide klassen zouden vervangbaar moeten zijn voor hun basistypen, dwz een verwijzing naar een basisklasse zou vervangbaar moeten zijn door een afgeleide klasse zonder het gedrag te beïnvloeden. Het Liskov-vervangingsprincipe vertegenwoordigt een sterke gedragssubtypering en werd geïntroduceerd door Barbara Liskov in het jaar 1987.

Volgens Barbara Liskov: "Wat hier gewenst is, is zoiets als de volgende substitutie-eigenschap: als voor elk object o1 van type S er een object o2 van type T is, zodat voor alle programma's P gedefinieerd in termen van T, het gedrag van P is ongewijzigd wanneer o1 wordt vervangen door o2, dan is S een subtype van T. "

Een klassiek voorbeeld van schending van het Liskov-substitutieprincipe is het rechthoek-vierkant-probleem. De klasse Square breidt de klasse Rectangle uit en gaat ervan uit dat de breedte en hoogte gelijk zijn.

Beschouw de volgende klas. De klasse Rectangle bevat twee gegevensleden: breedte en hoogte. Er zijn ook drie eigenschappen: hoogte, breedte en oppervlakte. Terwijl de eerste twee eigenschappen de hoogte en de breedte van de rechthoek instellen, heeft de eigenschap Area een getter die de oppervlakte van de rechthoek retourneert.

 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;

            }

         }    

    }

Een vierkant is een soort rechthoek waarvan alle zijden even groot zijn, dwz de breedte en hoogte van een vierkant is hetzelfde.

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;

            }

        }

    }

 Overweeg een andere klasse genaamd ObjectFactory.

 class ObjectFactory

    {

        public static Rectangle GetRectangleInstance()

        {

            return new Square();

        }

    }

Merk op dat de instellingen voor de eigenschappen Breedte en Hoogte in de klasse Vierkant zijn overschreven en gewijzigd om ervoor te zorgen dat de hoogte en breedte hetzelfde zijn. Laten we nu een instantie van de klasse Rectangle maken met en de eigenschappen hoogte en breedte instellen.

Rectangle s = ObjectFactory.GetRectangleInstance();

s.Height = 9;

s.Width = 8;

Console.WriteLine(s.Area);

Wanneer het bovenstaande codefragment wordt uitgevoerd, wordt de waarde 64 in de console weergegeven. De verwachte waarde is 72 aangezien de genoemde breedte en hoogte respectievelijk 9 en 8 zijn. Dit is een schending van het Liskov-vervangingsprincipe. Dit komt doordat de klasse Square die de klasse Rectangle heeft uitgebreid, het gedrag heeft gewijzigd. Om ervoor te zorgen dat het Liskov-vervangingsprincipe niet wordt geschonden, kan de klasse Square de klasse Rectangle uitbreiden, maar mag het gedrag niet worden gewijzigd. Het gedrag is veranderd door de instellingen voor zowel de eigenschappen Breedte als Hoogte te wijzigen. De waarden van hoogte en breedte zijn hetzelfde als het een vierkant is - ze zouden niet hetzelfde moeten zijn als het een rechthoek is.

Hoe lossen we dit op, dwz ervoor zorgen dat dit principe niet wordt geschonden? Welnu, u kunt een nieuwe klasse laten introduceren met de naam Quadrilateral en ervoor zorgen dat zowel de rechthoek- als de vierkante klassen de vierhoekklasse uitbreiden.

 public class Quadrilateral

    {

        public virtual int Height { get; set; }

        public virtual int Width { get; set; }

        public int Area

        {

            get

            {

                return Height * Width;

            }

        }

    } 

Nu moeten zowel de Rectangle- als de Square-klasse de Quadrilateral-klasse uitbreiden en de waarden van de Width en Height-eigenschappen op de juiste manier instellen. In wezen zouden de afgeleide klassen de nodige functionaliteit moeten hebben om waarden voor deze eigenschappen in te stellen op basis van het type vierhoekige instantie waarvoor u de oppervlakte moet berekenen. Merk op dat zowel de eigenschappen Hoogte als Breedte zijn gemarkeerd als virtueel in de klasse Quadrilateral, wat betekent dat deze eigenschappen moeten worden overschreven door de klassen die de klasse Quadrilateral afleiden.

Het Liskov-vervangingsprincipe is een uitbreiding van het Open-Close-principe en wordt geschonden als je code hebt geschreven die "niet geïmplementeerde uitzonderingen" genereert of als je methoden in een afgeleide klasse verbergt die als virtueel zijn gemarkeerd in de basisklasse. Als uw code voldoet aan het Liskov-vervangingsprincipe, heeft u veel voordelen. Deze omvatten: herbruikbaarheid van code, verminderde koppeling en eenvoudiger onderhoud.