SOLID: Liskov Substitution Principle (Zasada podstawiania Liskov)

Wprowadzenie

W poniższym artykule przedstawię trzecią z zasad SOLID – zasadę podstawiania Liskov. Jest to jedna z cięższych do zrozumienia zasad, dlatego oprócz teorii w artykule znajdziesz również przykład jej zastosowania.
Nazwa zasady pochodzi od nazwiska jej autorki, amerykańskiej programistki Barbary Liskov.

LSP – Liskov Substitution Principle (Zasada podstawiania Liskov)

"Funkcje, które używają wskaźników lub referencji do klas bazowych, muszą być w stanie używać również obiektów klas dziedziczących po klasach bazowych, bez dokładnej znajomości tych obiektów."

Zasada zakłada, że zawsze powinniśmy mieć możliwość użycia zamiast klasy bazowej, dowolnej jej podklasy, a podmiana taka nie powinna wpływać na poprawność działania naszej aplikacji.
W skrócie kod powinien działać zarówno dla samej klasy bazowej, jak i dla każdej jej implementacji.

Przykład zastosowania

Rozważmy aplikację, która zajmuje się odtwarzaniem muzyki oraz filmów.

//przykład łamiący zasadę LSP

public class MediaPlayer {
    public void playAudio() {
        //play audio code
    }

    public void playVideo() {
        //play video code
    }
}

public class VideoUnsupportedException extends RuntimeException {
    public VideoUnsupportedException(String message) {
        super(message);
    }
}

public class WindowsMediaPlayer extends MediaPlayer {
    //this player supports both video and audio
}

public class OnlyAudioPlayer extends MediaPlayer {

    @Override
    public void playVideo() {
        throw new VideoUnsupportedException("Playing video is not supported by this player.");
    }
}


import java.util.List;

public class MediaHandler {
    public void playVideoInAllMediaPlayers(List<MediaPlayer> mediaPlayers) {
        for (MediaPlayer mediaPlayer : mediaPlayers) {
            mediaPlayer.playVideo();
        }
    }
} 

W powyższym przykładzie zasada LSP zostaje naruszona. Dlaczego? Nie wszystkie podklasy klasy nadrzędnej obsługują odtwarzanie filmów wideo, co powoduje wyrzucenie wyjątku i przerwanie działania aplikacji.

Zobaczmy zatem jak aplikacja powinna wyglądać, kiedy zastosujemy się do zasady LSP.

//przykład z uwzględnieniem zasady LSP

public class MediaPlayer {
    public void playAudio() {
        //play audio code
    }
}

public class VideoMediaPlayer extends MediaPlayer {
    public void playVideo() {
        //play video code
    }
}

public class VideoMediaPlayer extends MediaPlayer {
    //this player supports both video and audio
}

public class OnlyAudioPlayer extends MediaPlayer {
    //this player supports only audio
    //there's no need to throw exception
}

public class MediaHandler {
    public void playVideoInAllMediaPlayers(List<VideoMediaPlayer> videoMediaPlayers) {
        for (VideoMediaPlayer player : videoMediaPlayers) {
            player.playVideo();
        }
    }
} 

Jak widać na powyższym kodzie utworzyliśmy teraz dwie klasy bazowe. Jedną z nich jest klasa MediaPlayer, która odtwarza tylko audio, druga z nich to VideoMediaPlayer, która zaś jest rozszerzeniem klasy MediaPlayer i dodatkowo implementuje metodę odpowiedzialną za odtwarzanie plików wideo – dzięki temu klasa ta może odtwarzać zarówno audio jak i wideo.
Dzięki takiemu rozwiązaniu pozbyliśmy się konieczności rzucania wyjątku, gdyż odtwarzacze wspierające tylko odtwarzanie dźwięku mogą rozszerzać klasę MediaPlayer, która teraz nie posiada implementacji metody odtwarzającej wideo.

Podsumowanie

Zasada LSP wydaje się być dość trudną do zrozumienia zasadą i bardzo często mylona jest z innymi zasadami SOLID, mianowicie zasadą OCP oraz zasadą ISP.
Należy pamiętać, że jeśli pisząc kod, kierujemy się zasadą podstawiania Liskov, nie powinien on posługiwać się żadnymi blokami warunkowymi w celu umożliwienia prawidłowego działania aplikacji. Dodatkowo powinniśmy mieć możliwość podstawienia dowolnego obiektu podrzędnego w miejscu obiektu bazowego, bez konieczności zadawania pytania jakiej klasy jest ten obiekt.

 

Leave a Comment

Your email address will not be published. Required fields are marked *