3. Zasada podstawienia Liskov
Zasada podstawienia Liskov (Liskov Substitution Principle, LSP).
Innymi słowy, klasa podrzędna powinna być w stanie zastąpić swoją klasę nadrzędną bez zmiany oczekiwanego zachowania programu.
Poniżej znajdziesz przykład zasady LSP w języku Java. Załóżmy, że mamy hierarchię klas reprezentujących prostokąty i kwadraty:
// Klasa łamiąca zasadę LSP
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 calculateArea() {
return width * height;
}
}
// Klasa dziedzicząca łamiąca zasadę LSP
class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width;
}
@Override
public void setHeight(int height) {
this.width = height;
this.height = height;
}
}
public class Main {
public static void main(String[] args) {
// Użycie klas łamiących zasadę LSP
Rectangle rectangle = new Square();
rectangle.setWidth(5);
rectangle.setHeight(10);
// Błąd: Oczekiwano, że prostokąt, ale jest to kwadrat
int area = rectangle.calculateArea();
System.out.println("Area: " + area);
}
}W powyższym przykładzie klasa Square dziedziczy po klasie Rectangle, ale narusza zasadę LSP poprzez nadpisywanie metod setWidth i setHeight. W efekcie obiekt typu Square, gdy jest używany zamiast obiektu typu Rectangle, prowadzi do nieprawidłowych wyników.
Aby naprawić to naruszenie, możemy ponownie zaprojektować hierarchię klas lub dostosować metody w sposób, który nie zmienia oczekiwanego zachowania dla klas podrzędnych.
Jednym z możliwych podejść jest wykorzystanie interfejsu, aby uniknąć problemów związanych z dziedziczeniem wielokrotnym:
// Interfejs zgodny z zasadą LSP
interface Shape {
void setWidth(int width);
void setHeight(int height);
int calculateArea();
}
class Rectangle implements Shape {
protected int width;
protected int height;
@Override
public void setWidth(int width) {
this.width = width;
}
@Override
public void setHeight(int height) {
this.height = height;
}
@Override
public int calculateArea() {
return width * height;
}
}
class Square implements Shape {
private int side;
@Override
public void setWidth(int width) {
this.side = width;
}
@Override
public void setHeight(int height) {
this.side = height;
}
@Override
public int calculateArea() {
return side * side;
}
}
public class Main {
public static void main(String[] args) {
// Użycie interfejsu zgodnego z zasadą LSP
Shape rectangle = new Rectangle();
rectangle.setWidth(5);
rectangle.setHeight(10);
int rectangleArea = rectangle.calculateArea();
System.out.println("Rectangle Area: " + rectangleArea);
Shape square = new Square();
square.setWidth(5);
square.setHeight(5);
int squareArea = square.calculateArea();
System.out.println("Square Area: " + squareArea);
}
}W tym poprawionym przykładzie używamy interfejsu Shape, który jest implementowany zarówno przez Rectangle, jak i Square.
To pozwala na zastępowanie obiektów bez zmiany oczekiwanego zachowania programu, spełniając zasadę podstawienia Liskov.