Definicja
Zasada podstawienia Liskov (ang. Liskov Substitution Principle, LSP) mówi, że obiekty klasy pochodnej powinny móc zastępować obiekty klasy bazowej bez zmiany poprawności działania programu.
Innymi słowy – jeśli klasa B dziedziczy po klasie A, to obiekt B powinien zachowywać się tak, by program, który działa z A, działał poprawnie także z B.


Przykład z życia
Wyobraźmy sobie system zarządzania prefabrykacją, który obsługuje różne typy elementów – belki, płyty i słupy.
Wszystkie mają wspólne cechy: wymiary, masę i funkcję transportu na plac budowy.
Jednak ktoś tworzy klasę „ElementTymczasowy”, która dziedziczy po „PrefabElement”, ale nie może być transportowana, bo to tylko element testowy (np. próbka).
Jeśli system spróbuje przetransportować każdy element w taki sam sposób, używając tej klasy, to program się „wywróci” — naruszy zasadę Liskov.
Każdy obiekt klasy pochodnej powinien zachowywać się spójnie z oczekiwaniami klasy bazowej.
Przykłady przed i po zastosowaniu zasady
PRZED
Poniższy przykład łamie zasadę Liskov, bo klasa TemporaryElement zmienia zachowanie metody transport, w sposób sprzeczny z klasą bazową.
Kod oczekuje, że każda instancja PrefabElement ma metodę transport, która działa poprawnie.
Jednak TemporaryElement zmienia jej zachowanie — powoduje wyjątek.
To łamie zasadę podstawienia Liskov, bo obiekt klasy pochodnej nie zachowuje się jak jego klasa bazowa.
class PrefabElement {
protected String name;
public PrefabElement(String name) {
this.name = name;
}
public void transport() {
System.out.println("Transporting " + name + " to construction site...");
}
}
class TemporaryElement extends PrefabElement {
public TemporaryElement(String name) {
super(name);
}
@Override
public void transport() {
// Naruszenie LSP — ta klasa nie powinna być transportowana!
throw new UnsupportedOperationException("Temporary elements cannot be transported!");
}
}
public class Main {
public static void transportElement(PrefabElement element) {
element.transport();
}
public static void main(String[] args) {
PrefabElement beam = new PrefabElement("Beam");
TemporaryElement testSample = new TemporaryElement("Test Sample");
transportElement(beam); // OK
transportElement(testSample); // Błąd! Naruszenie LSP
}
}

PO
Zamiast łamać zasadę LSP, można przebudować hierarchię klas tak, by tylko transportowalne elementy dziedziczyły po PrefabElement, a inne implementowały inny interfejs lub klasę bazową.
Teraz klasy są zaprojektowane zgodnie z zasadą LSP:
PrefabElementmożna bezpiecznie przetransportować,TemporaryElementistnieje niezależnie i nie psuje zachowania systemu.
Nie dochodzi do sytuacji, w której klasa pochodna łamie oczekiwania klasy bazowej.
abstract class Element {
protected String name;
public Element(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
abstract class TransportableElement extends Element {
public TransportableElement(String name) {
super(name);
}
public abstract void transport();
}
class PrefabElement extends TransportableElement {
public PrefabElement(String name) {
super(name);
}
@Override
public void transport() {
System.out.println("Transporting " + name + " to construction site...");
}
}
class TemporaryElement extends Element {
public TemporaryElement(String name) {
super(name);
}
public void analyze() {
System.out.println("Analyzing " + name + " in laboratory...");
}
}
public class Main {
public static void transportElement(TransportableElement element) {
element.transport();
}
public static void main(String[] args) {
PrefabElement beam = new PrefabElement("Beam");
TemporaryElement sample = new TemporaryElement("Test Sample");
transportElement(beam); // OK
// transportElement(sample); // Kompilator nie pozwoli – i o to chodzi!
sample.analyze(); // Działa niezależnie
}
}
Kiedy zasada jest ważna?
- Gdy budujesz hierarchię klas w systemie (np. różne typy prefabrykatów),
- Gdy stosujesz polimorfizm i chcesz mieć pewność, że zamiana obiektów nie zmieni zachowania programu.
Na co zwracać uwagę:
- Nie każda relacja między klasami powinna być dziedziczeniem,
- Klasy pochodne nie powinny ograniczać zachowania klas bazowych,
- Jeśli dana klasa nie może spełniać wszystkich wymagań bazowej – to znak, że potrzebna jest inna hierarchia.
Trudności:
- Początkujący często nadużywają dziedziczenia zamiast kompozycji,
- LSP wymaga myślenia o tym, jak obiekty będą używane w kontekście, a nie tylko jak są zbudowane.
