Rahul was sitting on his desk and working on the next version of his now-famous multiplayer game, Scavengers: Infinitesimal Wars. The USP of Scavengers is that users can play the game as various playable superhero characters of their choice.
Selena (Head of Product Team at Scavengers): “Hi Rahul, Our product team came up with many new playable characters which we know that our users are going to love, just take a look at this baby groot.”
Rahul: “That's awesome Selena, this will surely skyrocket our sales, I want this in production by next month.”
Mohan (lead developer at Scavengers): “Sure Rahul, our team will start the development by next week.”
Selena: “BTW, I forgot to mention that baby groot cannot attack or fly since he is soo small and cute. But he can heal others instead! please keep that in mind.”
This is how the Superhero classes are organized as of now
“Since we already knew that there can be multiple SuperHeros in the future, we had already designed the game in a way that it is easy to add new Characters by simply extending the SuperHero class. Glad that I already knew about The Open-Closed principle! This is going to be a piece of cake” — Mohan thought to himself
Based on the requirements shared by Selena, Mohan came up with the following implementation:
After testing code on his local system, Mohan submitted his changes to production later that evening and went home to sleep peacefully. Little did he know that his boss will be yelling at him the very next day, instead of congratulating him. He received a call the next morning!
Rahul: “Mohan, our production build is failing since your last change was pushed, our customers are complaining, we need to fix it asap!!” Mohan, half-asleep, quickly went to his laptop to see what went wrong and after scrolling through countless logs for hours, he found the problem :
💡 Before reading further, try thinking about how you will solve this
Hint: Updating the test is not a good idea!!
Mohan quickly realized that in his hastiness to complete the feature on time, he had violated an important SOLID principle, that is, the Liskov Substitution Principle (LSP). And he started remembering the teachings of his Master, the Wise Developer:
“In OOP, We rely on inheritance heavily for writing reusable and maintainable code. But what are the characteristics of best inheritance hierarchies? What are the traps that will cause us to create hierarchies that do not conform to the Open-Closed Principle? LSP tries to throw light on these questions. This term was coined by Barbara Liskov, who stated it as:
Let φ(x) be a property provable about objects x of type T. Then φ(y) should be true for objects y of type S where S is a subtype of T.
Or as Robert C.Martin phrased it :
Functions that use pointers of references to base classes must be able to use objects of derived classes without knowing it.
When do you violate LSP?
LSP reveals hidden problems with your inheritance structure. For example, what does it even mean to call the fly() method in Groot class? Isn’t it confusing? In fact, what about Spiderman ? he cannot fly() but he can swing(). And what if this swinging() behavior needs to be shared between SpiderMan and Venom (a villain) class?
Asking these questions makes one thing clear about our usecase, that is, ‘Different SuperHero/SuperVillain classes can have a different set of behaviors, and we need a way to pick and choose these behaviors.’ Since we cannot rely on inheritance here, a better strategy would be to delegate this responsibility to another class or set of classes. One Such implementation could be
LSP is all about well-designed inheritance, when you inherit from a base class, you should be able to substitute your derived class with the base class without things going wrong. Otherwise, you have used inheritance incorrectly! And when you use inheritance incorrectly,
Joe (another dev in the team): “I thought subclassing was the best way for code reuse, but now you are saying that it's bad?”
Mohan: “No, I am not saying that it's bad, LSP tells you when you should not subclass. And by looking at the latest requirements shared by Selena, It’s quite clear that we need to refactor.”
Joe: “I have seen this design somewhere else as well, is there any common name for it ?”
Mohan: “Yeah, good catch. This design pattern is commonly known as Strategy Pattern, and it relies on composition instead of inheritance to solve a problem.”
Joe: “So if I find any class that is breaking LSP, should I always depend on composition to solve the problem ?”
Mohan: “No it's not necessary, there are many other such patterns. But that’s a story for another time :)”