Liskov Substitution Principle (LSP)

Obiekty klasy pochodnej powinny być w stanie zastępować obiekty klasy bazowej bez wpływu na poprawność działania programu.

Jeśli mamy przykładową klasę Animal, a klasa Bird dziedziczy po Animal, to Bird powinna zachowywać się jak Animal, czyli powinna być używana wszędzie tam, gdzie można użyć Animal, bez zmiany logiki działania. LSP wymaga, aby klasa pochodna była zgodna z funkcjonalnością klasy bazowej i nie łamała kontraktu, jaki klasa bazowa ustala. Jeśli klasa pochodna zmienia zachowanie klasy bazowej w sposób, który nie jest zgodny z jej przeznaczeniem, łamiemy zasadę LSP.

Kod łamiący LSP:

class Bird {
    public void fly() {
        System.out.println("Bird is flying");
    }
}

class Penguin extends Bird {
    @Override
    public void fly() {
        throw new UnsupportedOperationException("Penguins cannot fly");
    }
}

W tym przykładzie mamy klasę Bird z metodą fly(). Klasa Penguin, która dziedziczy po Bird, ale nie może latać, więc rzuca wyjątek. To łamie zasadę Liskov, ponieważ obiekt klasy Penguin nie może być używany w miejscach, gdzie oczekujemy zachowania klasy Bird (czyli możliwości latania).

Poprawne LSP:

abstract class Bird {
    public abstract void eat();
}

class FlyingBird extends Bird {
    public void fly() {
        System.out.println("This bird is flying");
    }

    @Override
    public void eat() {
        System.out.println("This bird is eating");
    }
}

class Penguin extends Bird {
    @Override
    public void eat() {
        System.out.println("Penguin is eating");
    }
}

W tej wersji Penguin dziedziczy po Bird, ale nie ma metody fly(), ponieważ pingwin nie lata. FlyingBird dziedziczy po Bird i implementuje metodę fly(). Dzięki temu unikamy sytuacji, w której klasa pochodna (Penguin) łamie oczekiwania klasy bazowej.

Zasada LSP w kontekście SOLID:

Liskov Substitution Principle (LSP) jest kluczowa dla SOLID, ponieważ zapewnia, że nasze systemy są elastyczne i skalowalne. Przestrzeganie tej zasady umożliwia:

  • Poprawne dziedziczenie: Klasy pochodne muszą w pełni respektować zachowania klas bazowych, co sprawia, że nasz system jest bardziej przewidywalny i mniej podatny na błędy.
  • Polimorfizm: Dzięki LSP możemy bezpiecznie używać polimorfizmu. Obiekt klasy pochodnej może zastępować obiekt klasy bazowej bez obaw o zmianę logiki programu.

Kluczowe zasady LSP:

  • Nie zmieniaj kontraktu klasy bazowej: Klasy pochodne muszą przestrzegać kontraktów, jakie narzuca klasa bazowa (np. nie powinny rzucać wyjątków w sytuacjach, w których klasa bazowa tego nie robi).
  • Zgodność interfejsów: Jeśli klasa bazowa definiuje pewne zachowania, klasa pochodna musi je wspierać i działać zgodnie z nimi.

Podsumowanie:

Zasada Liskov Substitution Principle (LSP) w kontekście SOLID jest istotna, ponieważ zapewnia, że klasy pochodne mogą być stosowane zamiennie z klasami bazowymi bez ryzyka wprowadzenia błędów lub niespójności w systemie. Poprawna implementacja LSP prowadzi do lepszego projektowania opartego na polimorfizmie, większej elastyczności i bardziej niezawodnego kodu.