Liskov Substitution Principle to prawdopodobnie jedna z najbardziej pokręconych zasad mnemonika SOLID. Po raz pierwszy została sformułowana przez Barb

Po raz pierwszy została sformułowana przez Barbarę Liskov w 1987 roku (za Wikipedią), stąd też jej nazwa. W polskim tłumaczeniu nazywamy ją zasadą podstawienia Liskov. Reguła została spopularyzowana przez Roberta C. Martina w artykule Principles of Object Oriented Design oraz książce Agile Software Development: Principles, Patterns, and Practices.

 

Zasadnicza treść LSP brzmi następująco:

 

„Funkcje, które używają wskaźników lub referencji do klas bazowych, muszą być w stanie używać również obiektów klas dziedziczących po klasach bazowych, bez dokładnej znajomości tychobiektów”.

 

Podejrzewam, że dla większości osób może być ona początkowo zagmatwana, dlatego postaram się przybliżyć, o co chodziło autorce, a później także mistrzowi Martinowi.  

 

Liskov Substitution Principle

 

Zasadę LSP można spróbować przedstawić prościej. Klasa dziedzicząca powinna tylko rozszerzać możliwości klasy bazowej i w pewnym sensie nie zmieniać tego, co ona robiła już wcześniej. Mówiąc jeszcze inaczej — jeśli będziemy tworzyć egzemplarz klasy potomnej, to niezależnie od tego, co znajdzie się we wskaźniku na zmienną, wywoływanie metody, którą pierwotnie zdefiniowano w klasie bazowej, powinno dać te same rezultaty. LSP można też rozpisać za pomocą krótkiego, abstrakcyjnego przykładu:

 

- Mamy klasę A z metodą MyMethod, która zwraca wartość Z.

- Tworzymy klasę B, która dziedziczy z A.

- Niezależnie od sposobu utworzenia klasy A oraz B:

- A a = new A()

- A b1 = new B()

- B b2 = new B()

Musi zajść równość: a.MyMethod() == b1.MyMethod() == b2.MyMethod()

W innym przypadku dochodzi do pogwałcenia zasady Liskov Substition Principle.  

 

Antyprzykład

 

Napisałem wyżej, o co chodzi w zasadzie Liskov Substition Principle. Dałem też krótki, abstrakcyjny przykład, który pokazywał, w jaki sposób można dopasować się do tej reguły. Z kolei teraz chcę posłużyć się standardowym dla LSP antyprzykładem, który krąży po sieci. Dla mnie bazą będzie listing przedstawiony na stronie OOdesign.com:

 


class Rectangle

{

     protected int _width;

     protected int _height;

     public void SetWidth(int width)

     {

         this._width = width;

     }

     public void SetHeight(int height)

     {

         this._height = height;

     }

     public int GetWidth()

     {

         return this._width; 

     }

     public int GetHeight()

     {

         return this._height;

     }

     public int GetArea()

     {

         return this._width * this._height;

     }

}

class Square: Rectangle

{

      public void SetWidth(int width)

     {

         this._width = width;

         this._height = width;

     }

     public void SetHeight(int height)

     {

         this._width = height;

         this._height = height;

     }

}

Oraz kod klasy Program:


public class Program

{

      public static void Main(string[] args)

      {

          // Wskaźnik na Rectangle i tworzymy obiekt klasy Rectangle

          Rectangle r = new Rectangle();

          r.SetWidth(5);

          r.SetHeight(10);

          Console.WriteLine(r.GetArea());

 

          // Wskaźnik na Rectangle, ale tworzymy obiekt klasy Square

          Rectangle rs = new Square();

          rs.SetWidth(5);

          rs.SetHeight(10);

          Console.WriteLine(rs.GetArea());

 

          // Wskaźnik na Sqaure i tworzymy obiekt klasy Square

          Square s = new Square();

          s.SetWidth(5);

          s.SetHeight(10);

          Console.WriteLine(s.GetArea());

       }

}

 

 

Niby nie ma w tym kodzie nic skomplikowanego i jest bardzo krótki, jednak w tych kilkudziesięciu liniach łamana jest zasada LSP, ponieważ osiągany rezultat nie zawsze jest taki sam!  

 

Powyższy kod będzie również wyrzucać ostrzeżenia o niezamierzonej chęci przykrycia metod SetWidthoraz SetHeight. Oczywiście możemy je naprawić, dodając w klasie bazowej słowo kluczowe virtual, natomiast w klasie dziedziczącej — override. Spowoduje to, że przykłady 2. i 3. będą zwracały tę samą wartość. Reguła LSP będzie wciąż jednak pogwałcona, ponieważ przykład A będzie zwracał inną wartość dla tej samej metody.   Podsumujmy: rozszerzając, nie modyfikujmy sposobu działania istniejącego kodu.  

Jerzy Piechowiak

Altcontroldelete.pl   

 

 Szukasz książki lub kursu do C#? Kliknij TUTAJ!