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.