Romman Sabbir
Romman Sabbir | Official

Romman Sabbir | Official

SOLID Principles (Series) [PART 3]

SOLID Principles (Series) [PART 3]

SOLID - make Object-Oriented designs more understandable, flexible and maintainable.

Romman Sabbir's photo
Romman Sabbir
·Oct 20, 2022·

3 min read

Subscribe to my newsletter and never miss my upcoming articles

Table of contents

  • What is LSP?
  • Example: Bad Design and Implementation
  • Example: Good Design and Implementation
  • Recap

In the last article, we talked about Open-Closed Principle which is one of the key concepts in OOP that enables us to write robust, maintainable and reusable software components. But following the rules of that principle alone is not enough to ensure that you can change one part of your system without breaking other parts. Our classes and interfaces also need to follow the Liskov Substitution Principle to avoid any side-effects. LSP extends OCP by focusing on the behavior of a superclass and its subtypes.

What is LSP?

LSP defines that objects of a superclass shall be replaceable with objects of its subclasses without breaking the application. That requires the objects of your subclasses to behave in the same way as the objects of your superclass.

Let's simplify this the scenario.

  • This principle applies to inheritance hierarchies and is just an extension of the Open Close Principle.

  • It means that we must make sure that new derived classes are extending the base classes without changing their original behavior. Basically, derived classes should never do less than their base class.

  • If a subtype of the supertype does something that the client of the supertype does not expect, then this is in violation of LSP. Imagine a derived class throwing an Exception that the superclass does not throw, or if a derived class has some unexpected side effects. One has to consider that how the client programs are using the class hierarchy. Sometimes code refactoring is required to fix identified LSP violations.

Example: Bad Design and Implementation

Let's think in-terms of a MediaPlayer

interface MediaPlayer {
    fun playAudio(mediaContent : MediaContent)
    fun playVideo(mediaContent : MediaContent)
}

Let's work on two media player VLC Media Player and Winamp Media Player.

[Note: Winamp doesn't support Video]

class VLCMediaPlayer : MediaPlayer {
    @override fun playAudio(mediaContent : MediaContent) {...}
    @override fun playVideo(mediaContent : MediaContent) {...}
}
class WinampMediaPlayer : MediaPlayer {
    @override fun playAudio(mediaContent : MediaContent) {...}
    @override fun playVideo(mediaContent : MediaContent) {
        throw VideoNotSupportedException()
    }
}
class VideoNotSupportedException(@override val message = "Winamp doesn't support Video") : RuntimeException() {}

Everything looks good and okay, right? No!. Why? Let's find out.

  • When we pass a audio MediaContent to the MediaPlayer, MediaPlayer will try to play the audio MediaContent. Both VLCMediaPlayer and WinampMediaPlayer support audio playing functionality.
  • But when we a video MediaContent to the MediaPlayer, MediaPlayer will try to play the video MediaContent. VLCMediaPlayer will be able to play the video MediaContent but when it's about WinampMediaPlayer it will throw VideoNotSupportedException.
  • That's where LSP is violated in WinampMediaPlayer, becuase it's breaks the original behavior of the super class MediaPlayer.

So, how do we solve this "breaking"?

Example: Good Design and Implementation

interface MediaPlayer {
    fun playAudio(mediaContent : MediaContent)
}
interface VideoMediaPlayer : MediaPlayer {
    fun playVideo(mediaContent : MediaContent)
}
class VLCMediaPlayer : VideoMediaPlayer {
    @override fun playAudio(mediaContent : MediaContent) {...}
    @override fun playVideo(mediaContent : MediaContent) {...}
}
class WinampMediaPlayer : MediaPlayer {
    @override fun playAudio(mediaContent : MediaContent) {...}
}

So, what's the changes here?

  • Previous MediaPlayer functionality has been divided into two part now, where MediaPlayer is responsible to play audio MediaContent and VideoMediaPlayer (extend MediaPlayer) is responsible to play both audio and video MediaContent.

  • VLCMediaPlayer supports audio and video MediaContent as expected and WinampMediaPlayer supports only audio MediaContent.

  • Now, here LSP is not violated in WinampMediaPlayer, becuase it's doesnt breaks the original behavior of the super class MediaPlayer.

Recap

LSP defines that objects of a superclass shall be replaceable with objects of its subclasses without breaking the application. That requires the objects of your subclasses to behave in the same way as the objects of your superclass.

In the next article, we will talk about Interface Segregation Principle.

That's it for today. Happy Coding...

 
Share this