Liskov’s Substitution Principle: SOLID Design Principles

Share the Article

What is Liskov’s Substitution Principle

Liskov’s substitution principle is one of the very basic principle for object oriented application designing. It is also contributing third letter “L” in SOLID acronym where O stand for Liskov’s. This principle tries to address issues due to class extension.

Main idea of Liskov’s substitution principle is that subclass should qualify to be the actual substitution for the base class. This means, during class extension in real world, we need to ensure that derived object should substitute base class object without impacting base class functionality. Therefore, the derived class must support all feature of base class. In any case, the derived class should not block or restrict any of the base class features.

Liskov’s substitution principle acts like, one more check for inheritance. It will support correct derivation of the base class. Due to correct inheritance, the maintainability of the classes for future extension becomes very easy and economical.

Why

Lets us try to understand the need for Liskov’s substitution principle by taking an example of an application having Cricket class with below features :

  • Number of players.
  • Equipment’s required for Cricket.

Below is the class details:

class Cricket { int m_player; public: void setPlayer(int countPlayer) { m_player = countPlayer; } void setEquipment() { cout<<"Added Bat and Ball"<<endl; } };

Later on client adds logic for checking rain condition. Hence, additionally provide rain protection to players if rains are happening.

Sample Example without using Liskov’s Substitution Principle

Below is the sample application. This application provides rain protection as an additional feature.

#include<iostream> //main header for io using namespace std; //for namespace class Cricket { int m_player; public: void setPlayer(int countPlayer) { m_player = countPlayer; } void setEquipment() { cout<<"Added Bat and Ball"<<endl; } }; void RainProtecion(Cricket &c) { cout<<"Giving rain protection for all players"<<endl; } int main() //Client Code { Cricket c1; c1.setPlayer(11); c1.setEquipment(); cout<<"If it is raining "<<endl; RainProtection(c1); return 0; }

Suppose, in future, the application needs to support Badminton also. Similarly, we need provision for setting player and setting their equiptments. Since the Cricket class is already having similar interface, therefore, the new class Badminton can be easily derived from it.

Below is the sample code by extending the functionality for Badminton:

#include<iostream> //main header for io using namespace std; //for namespace class Cricket { int m_player; public: void setPlayer(int countPlayer) { m_player = countPlayer; } void setEquipment() { cout<<"Added Bat and Ball"<<endl; } }; class Badminton: public Cricket { public: void setEquipment() { cout<<"Added Bat and Shuttle"<<endl; } }; void RainProtection(Cricket &c) { cout<<"Giving rain protection"<<endl; } int main() //Client Code { Cricket c1; c1.setPlayer(11); c1.setEquipment(); Badminton b1; b1.setPlayer(2); b1.setEquipment(); cout<<"If it is raining "<<endl; RainProtection(c1); RainProtection(b1); //This is logically wrong return 0; }

Since, the Badminton is inherited from Cricket class, so we will be able to pass badminton object for RainProtection( ) functionality.

In reality, the badminton is an indoor game and cricket is an outdoor game. Rain protection is not a valid feature for Badminton. Hence, the Badminton is not fully compatible with Cricket (base) class.

Therefore, we are violating the Liskov principle here. Since derived class object is not fully compatible with base class, the substitution shall fail. Above hierarchy is dangerous and it will lead to run time surprises in given application . According to Liskov substitution principle- if base class A is having a1 as object and derived class B is having b1 as object, a1 should always be completely replaced by b1. If complete substitution is not possible then current derivation is risky.

How

Lets us try to update the above application with Liskov substitution principle. We need to follow below rules in order to create correct hierarchy with ensured future maintainability.

Create abstract class with generic interface

Create an abstract base class Game which will have all abstract interfaces generic to different games.

Class Game { int m_player; bool m_outdoor; public: void setPlayer(int countPlayer) { m_player = countPlayer; } virtual void setEquipment() = 0; void setOutDoor(bool outdoor) { m_outdoor = outdoor; } bool getOutdoor() { return m_outdoor; } };

AS seen above, now we have base class which support both indoor and outdoor, so it is easy to extend this class.

Derive concrete classes

Derive cricket and badminton from base class Game

class Game { }; class Cricket : public Game { }; class Badminton : public Game { };

Same application now using Liskov’s Substitution Principle

Below is the updated application which will deal with future extensibility also:

#include<iostream> //main header for io using namespace std; //for namespace class Game { int m_player; bool m_outdoor; public: void setPlayer(int countPlayer) { m_player = countPlayer; } virtual void setEquipment() = 0; void setOutDoor(bool outdoor) { m_outdoor = outdoor; } bool getOutdoor() { return m_outdoor; } }; class Cricket: public Game { public: virtual void setEquipment() { cout<<"Added Bat and Ball"<<endl; } }; class Badminton: public Game { public: virtual void setEquipment() { cout<<"Added Bat and Shuttle"<<endl; } }; void RainProtection(Game &c) { if(c.getOutdoor() == true) cout<<"Giving rain protection"<<endl; else cout<<"Rain protection not required"<<endl; } int main() { Cricket c1; c1.setPlayer(11); c1.setEquipment(); c1.setOutDoor(true); Badminton b1; b1.setPlayer(2); b1.setEquipment(); b1.setOutDoor(false); cout<<"If it is raining"<<endl; RainProtection(c1); RainProtection(b1); return 0; }

As seen above, suppose now, if we have to support more games, such as, Golf, Hockey, etc. then we can very easily derive them from Game and use in.

Pros & Cons of Liskov’s Substitution Principle

Pros

  • Firstly, it ensures correct sub-hierarchy with relevant checks.
  • Secondly, it becomes easy to extend new classes.
  • Thirdly, the maintainability of code becomes very easy.
  • Finally, there shall be no runtime surprises in the application.

Cons

  • Sometimes we need to update base class also in order to support new extension.

Main Funda: Liskov’s Substitution Principle ensures that the derived classes are not restricting any base function.

Advanced C++ Topics

Single Responsibility Principle
Open Closed Principle
Singleton Design Pattern
Factory Method Design Pattern
Builder Design Pattern
How std::forward( ) works?
How std::move() function works?
What is reference collapsing?

Share the Article

Leave a Reply

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