SOLID- Liskov substitution principle

Zasada podstawienia Liskov, jeżeli klasa P dziedziczy po klasie T, to obiekt klasy T powinien być zastępowalny przez obiekt klasy P, bez wpływu na prawidłowe działanie programu. Innymi słowy tam, gdzie używa się klasy rodzica, można zastąpić ją dowolną klasą pochodną.

W kodzie nie powinno być potrzeby sprawdzenia jakiej konkretnie klasy jest dany obiekt dziedziczący, żeby zmodyfikować działanie metody. Będzie to jedna z sytuacji, która łamie zasady Liskov.

Przykład złego kodu:

public class Animal{
    public abstract void walk();
    public void eat() {
        System.out.println("going to eat");
    }
}

public class Lion extends Animal{
    @Override
    public void walk() {
        System.out.println("Lion is walking");
    }
    @Override
    public void eat() {
        super.eat();
        System.out.println("Lion eats meat");
    }
}

public class Whale extends Animal{ 
    @Override
    public void walk() {
    } 
    @Override
    public void eat() {
        super.eat();
        System.out.println("Whale eats fish");
    }
}

public class AnimalsActions{
    public void letAnimalWalk(List<Animal> animals){
        for (Animal animal : animals) {
            if (animal instanceof Whale){
               System.out.println("whales swim not walk!");
            }
            else{
               animal.walk();
            }
        }

    public void letAnimalEat(List<Animal> animals){
        for (Animal animal : animals) {
            animal.eat();
        }
    }
}

W powyższym przykładzie przy wywoływaniu metody walk() musimy sprawdzić jakie to zwierzę, bo wieloryb oczywiście nie może chodzić. Jest to niewydajne i niepoprawnie napisany kod. Sprawdźmy jak można go naprawić.

Poprawny kod

public class Animal{
    public void eat() {
        System.out.println("going to eat");
    }
}

public interface LandAnimal{
    public abstract void walk();
}

public interface WaterAnimal{
    public abstract void swim();
}

public class Lion extends Animal implements  LandAnimal{
    @Override
    public void walk() {
        System.out.println("Lion is walking");
    }
    
    public void eat() {
        super.eat();
        System.out.println("Lion eats meat");
    }
}

public class Whale extends Animal implements WaterAnimal{ 
    @Override
    public void swim() {
        System.out.println("Whale is swimming");
    } 
    
    public void eat() {
        super.eat();
        System.out.println("Whale eats fish");
    }
}

public class AnimalsActions{
    public void letAnimalWalk(List<LandAnimal> landAnimals){
        for (LandAnimal animal : landAnimals) {
           animal.walk();
        }
    }

     public void letAnimalSwim(List<WaterAnimal> waterAnimals){
        for (WaterAnimal animal : waterAnimals) {
           animal.swim();
        }
     }

    public void letAnimalEat(List<Animal> animals){
        for (Animal animal : animals) {
            animal.eat();
        }
    }
}

W poprawionej wersji naszego kodu widzimy, że mamy podział na zwierzęta wodne i lądowe, ale też klasę nadrzędną Animal, w której nieważne jakie zwierze się pojawi, zawsze wykona się poprawnie metoda eat(). Gdy stworzymy obiekt klasy Animal, również metoda eat() się wykona, tak samo metoda wykona się dla klas dziedziczących Whale oraz Lion, więc klasa Animal jest zastępowalna przez klasy po niej dziedziczące.

Dodaj komentarz