SOLID - Zasada podstawiania Liskov
Kluczowa zasada do zapewnienia elastyczności i rozszerzalności kodu. Umożliwia tworzenie hierarchii klas, które mogą efektywnie współpracować i być rozbudowywane.
S – Zasada pojedynczej odpowiedzialności (Single Responsibility Principle – SRP)
O – Zasada otwarte-zamknięte (Open-Close Principle – OCP)
L – Zasada podstawiania Liskov (Liskov Substitution Principle – LSP)
I – Zasada segregacji interfejsu (Interface Segregation Principle – ISP)
D – Zasada odwrócenia zależności (Dependency Inversion Principle – DIP)
Definicja
Każda klasa pochodna powinna być zastępowalna przez swoją klasę bazową bez naruszania poprawności programu.
Oznacza to, że klasa pochodna powinna zachowywać się tak samo jak klasa bazowa i nie wprowadzać żadnych nieoczekiwanych zmian lub ograniczeń. Dodatkowo warunki wstępne w podklasie nie mogą być trudniejsze do spełnienia niż w klasie po której dziedziczy, a warunki końcowe w podklasie nie mogą być łatwiejsze do spełnienia niż w klasie w bazowej. Jeśli nie klasa pochodna jest zastępowalna przez swoją klasę bazową, to łamie zasadę LSP i powoduje problemy z polimorfizmem, dziedziczeniem i abstrakcją.
Przykład
Stwórzmy klasę Bird ze wspólnymi cechami ptaków, w tym metodą fly(), która będzie rozszerzać klasy Sparrow i Penguin.
public class Bird {
//Wspólne cechy ptaków
String name;
public void fly(){
System.out.println(name + " leci");
}
} public class Sparrow extends Bird {
public Sparrow() {
this.name = "Elemelek";
}
} public class Penguin extends Bird{
public Penguin() {
this.name = "Jakub";
}
@Override
public void fly() {
throw new UnsupportedOperationException("Pingwin nie potrafi latać");
}
} Klasa ta łamie zasadę LSP, ponieważ klasa Penguin zmienia zachowanie metody fly() i wyrzuca wyjątek, przez co nie jest zastępowalna przez swoją klasę bazową Bird.
Aby naprawić ten kod nalezy zmienić hierarchię klas i wydzielić wspólne cechy ptaków do interfejsów lub klas abstrakcyjnych. W ten sposób klasy pochodne będą implementować tylko te metody, które są dla nich odpowiednie i nie będą naruszać kontraktu klasy bazowej.
W poprawionym kodzie dodałam interfejs FlyingBird do której przeniosłam metodę fly(). Z kolei w klasie Bird dodałam metodę eat(), która będzie wspólna dla wszystkich obiektów dziedziczących po niej
public class Bird {
//Wspólne cechy ptaków
String name;
public Bird(String name) {
this.name = name;
}
public void eat(){
System.out.println(name + " je.");
}
} public interface FlyingBird{
public void fly();
} public class Sparrow extends Bird implements FlyingBird {
public Sparrow(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + " leci");
}
} public class Penguin extends Bird{
public Penguin(String name) {
super(name);
}
} Przykład wywołania:
public class App
{
public static void main( String[] args )
{
Sparrow sparrow = new Sparrow("Elemelek");
Penguin penguin = new Penguin("Jakub");
List<Bird> birdList = new ArrayList<>();
birdList.add(sparrow);
birdList.add(penguin);
List<FlyingBird> flyingBirds = new ArrayList<>();
flyingBirds.add(sparrow);
// akcja niedozwolona
// flyingBirds.add(penguin);
for(Bird bird: birdList){
bird.eat();
}
for(FlyingBird flyingBird: flyingBirds){
flyingBird.fly();
}
}
} Teraz klasa Penguin nie łamie zasady LSP, ponieważ nie implementuje w żaden sposób metody fly(), której nie mogłaby wywołać w poprawny sposób.
Dzięki zastosowaniu zasady podstawienia Liskov kod stał się znacznie prostszy do rozszerzenia i testowania.
[…] L – Zasada podstawiania Liskov (Liskov Substitution Principle – LSP) […]
[…] L – Zasada podstawiania Liskov (Liskov Substitution Principle – LSP) […]
[…] L – Zasada podstawiania Liskov (Liskov Substitution Principle – LSP) […]
[…] L – Zasada podstawiania Liskov (Liskov Substitution Principle – LSP) […]