Liskov Substitution Principle (LSP)

Zasada podstawienia Liskov jest trzecią z pięciu podstawowych zasad programowania obiektowego ukrytych pod akronimem SOLID. LSP czyli Liskov Substitution Principle mówi o tym, że 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 tych obiektów.

Przykład kodu bez zastosowania LSP:

public class Pracownik {
   public void ObliczWyplate() {
       System.out.println(“Obliczam wypłatę dla pracownika”);
   }

   public void WyswietlNumerIdentyfikacyjny() {
       System.out.println(“Wyświetlam numer identyfikacyjny pracownika.”);
   }
}
public class Programista extends Pracownik() {
   @Override
   public void ObliczWyplate() {
       System.out.println(“Obliczam wypłatę dla programisty”);
   }

   @Override
   public void WyswietlNumerIdentyfikacyjny() {
       System.out.println(“Wyświetlam numer identyfikacyjny programisty.”);
   }
}
public class Wolontariusz extends Pracownik() {
   @Override
   public void ObliczWyplate() {
       throw new NotImplementedException();
   }

   @Override
   public void WyswietlNumerIdentyfikacyjny() {
       System.out.println(“Wyświetlam numer identyfikacyjny wolontariusza.”);
   }
}

Jak widać w powyższym kodzie w klasie Wolontariusz mamy problem z metodą ObliczWyplate(), ponieważ jak dobrze wiemy wolontariusz nie otrzymuje wynagrodzenia za swoją pracę.

Przykładowy kod zgodny z zasadą LSP:

public interface IOdwiedzajacy {
   void WyswietlNumerIdentyfikacyjny();
}
public interface IPlatnyPracownik {
   void ObliczWyplate();
}
public class Programista implements IOdwiedzajacy, IPlatnyPracownik {
   public void WyswietlNumerIdentyfikacyjny() {
       System.out.println(“Wyświetlam numer identyfikacyjny programisty.”);
   }

   public void ObliczWyplate() {
       System.out.println(“Obliczam wypłatę dla programisty”);
   }
}
public class Wolontariusz implements IOdwiedzajacy {
   public void WyswietlNumerIdentyfikacyjny() {
       System.out.println(“Wyświetlam numer identyfikacyjny wolontariusza.”);
   }
}

Dzięki temu, że rozbiliśmy klasę Pracownik na dwa interfejsy – IOdwiedzający oraz IPlatnyPracownik możemy bez problemu obsłużyć klasę z naszym bezpłatnym pracownikiem – Wolontariuszem. Nie musimy w jego przypadku wyrzucać wyjątku lub pozostawiać pustą metodę ObliczWyplate() co było naruszeniem zasady LSP.

Warunki wstępne

Do naruszenia trzeciej podstawowej zasady programowania obiektowego dochodzi również w przypadku zaostrzenia warunków wstępnych. Zobaczmy to na przykładzie:

public class Rodzic {
   public int ObliczCeneKoncowa(int cena, int rabat) throws Exception {
       if (cena == 0) {
           throw new Exception();
       }
       return cena - rabat;
   }
}

W powyższym przykładzie widzimy, że metoda ObliczCeneKoncowa rzuci nam wyjątkiem tylko w sytuacji gdy cena jest równa 0.

public class Dziecko extends Rodzic {
   @Override
   public int ObliczCeneKoncowa(int cena, int rabat) throws Exception {
       if (cena == 0 || cena < 20) {
           throw new Exception();
       }
       return cena - rabat;
   }
}

Klasa Dziecko dziedziczy po klasie Rodzic i nadpisuje metodę ObliczCeneKoncowa(). W wyniku nadpisania metody następuje zaostrzenie warunku i teraz wyjątek jest rzucany zarówno kiedy cena jest równa 0 oraz kiedy jest niższa niż 20. Reguła zapobiegająca naruszeniu LSP dotycząca warunków początkowych mówi, że w klasie pochodnej nie wolno zaostrzać warunków wstępnych. Można je osłabiać, ale nie wzmacniać.

Warunki końcowe

Do złamania LSP dochodzi również w sytuacji, gdy w klasie pochodnej osłabiono warunki końcowe. Zerknijmy na kod:

public class Rodzic {
   public int ObliczCeneKoncowa(int cena, int rabat) throws Exception {
       int cenaKoncowa = cena - rabat;
       if (cenaKoncowa <= 0) {
           throw new Exception();
       }
       return cenaKoncowa;
   }
}

W tej sytuacji metoda rzuca wyjątkiem, gdy cena końcowa jest mniejsza lub równa 0.

public class Dziecko extends Rodzic {
   @Override
   public int ObliczCeneKoncowa(int cena, int rabat) throws Exception {
       int cenaKoncowa = cena - rabat;
       return cenaKoncowa;
   }
}

Klasa Dziecko w wyniku nadpisywania metody z klasy Rodzic zrezygnowała z warunku końcowego, tak więc nie rzuci wyjątkiem gdy cena końcowa będzie wynosiła 0 lub spadnie poniżej. Jest to naruszenie zasady LSP.

Scroll to Top