SOLID Principles (Series) [PART 3]
SOLID - make Object-Oriented designs more understandable, flexible and maintainable.
![SOLID Principles (Series) [PART 3]](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1665554558214%2FICBbB8Zoe.jpg&w=3840&q=75)
Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.
Search for a command to run...
SOLID - make Object-Oriented designs more understandable, flexible and maintainable.
![SOLID Principles (Series) [PART 3]](/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1665554558214%2FICBbB8Zoe.jpg&w=3840&q=75)
Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.
No comments yet. Be the first to comment.
In this series SOLID Principles with real life example. Happy learning...
SOLID - make Object-Oriented designs more understandable, flexible and maintainable.
A practical guide to choosing the right coroutine primitive for your Android/Kotlin projects

This guide explains what Channels are, when to use them, how to use them correctly, and when NOT to use them β in simple, professional language. 1. What problem do Channels solve? In Kotlin coroutines, you often have multiple coroutines running at t...

Learn how to build preview-safe ViewModels in Jetpack Compose using Hilt.

HandlerInterceptor vs Filter in Spring Boot: A Practical Guide with JNDI Injection Prevention

Simple In-Memory Caching: A Tiny Trick with Massive Impact

If get to know something new by reading my articles, don't forget to endorse me on LinkedIn
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.
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.
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"?
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.
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...