SOLID- Liskov’s Substitution Principle (LSP)

Share

The principle states that

functions that use references to base classes must be able to use objects of derived classes without knowing it.

In simple terms, the principle says that if your code is calling a method residing in a base class than your code must be able to call the same method  if you would replace reference of the base class with a reference of any of its derived classes i.e at any given place in your code, you should be able to replace reference of a base class with a reference of any of its derived classes without  affecting the execution of the program. The principle seems difficult to understand from its definition but is the easiest one among all. The following example would talk all about LSP.

Benefits of following LSP:

1. Your class hierarchies would be easy to understand. Any new person in the project would quickly and easily understand it.

2. It would confirm to the perfect class hierarchies which inturns confirm to ease of code maintenance.

3. Preventing subtyping related bugs.

Explanation:

Let us go through the example to understand LSP in detail: suppose you have written a Vehicle class as follows which returns cost of the vehicle and the petrol consumption by the vehicle per Mile.


class Vehicle {

	public void vehicleCost()
	{

		//code to get you on road cost of the vehicle
	}

	public void petrolConsumptionPerMile()
	{

		//code to get you Petrol consumption per Mile
		// by vehicle
	}
}

Now, you would extend the Vehicle class in the HONDACivic and CHEVROLETCruze classes as follows,


class HONDACivic extends Vehicle { }

class CHEVROLETCruze extends Vehicle
{

	petrolConsumptionPerMile()
	{

		throw new UnsupportedOperationException();
	}

	dieselConsumtionPerMile()
	{

		//code to get you Diesel consumption per Mile
	    // by the Chevrolet Cruze car
	}
}

Everything is fine, your code would absolutely work when you test the classes with below class:


public VehicleTest
{

	public static void main(String[] args)
	{

		List vehicleList = new ArrayList();

		vehicleList.add(new HONDACivic());
		vehicleList.add(new CHEVROLETCruze());

		getFuelConsumption ( vehicleList );
	}

	static void getFuelConsumption ( List vehicleList )
	{

		for ( int i=0; i < vehicleList.size(); i++ )
		{

			Vehicle v = vehicleList.get(i);
			v.petrolConsumptionPerMile();
		}
	}
}

In the above main function; you are framing a list objects and adding HONDACivic and CHEVROLETCruze object references and calling the method petrolConsumptionPerMile. Definitely the code would not break because you have taken utmost care while writing the classes. Now, you would deal with two categories of Vehicle – PetrolVehicle and DieselVehicle than dealing with a Vehicle class.

But, the program is a clear violation to LSP:

You know that HONDACivic is a petrol car and CHEVROLETCruze is a diesel car… So, when you make a call to PetrolConsumptionPerMile of a base class using HONDACivic reference, you get the petrol consumption but for CHEVROLETCruze you would get unsupported exception as this does not operate on petrol but diesel. It’s the clear violation of LSP as by replacing Vehicle class reference to its derived class CHEVROLETCruze class, program is calling petrolConsumptionPerMile method present in CHEVROLETCruze class and not the one present in the Vehicle class. What could be the consequences of such a class hierarchy:

1. Logically, both derived classes can extend Vehicle class, but if there is a functionality lying in a base class which a derived class should not support then definitely it’s not the correct class hierarchy and, in such a case it does make a sense to redefine the hierarchy.

2. In this present case, CHEVROLETCruze does not support petrolConsumptionPerMile method of the base class and it has written the same method in it (which says unsuppported exception). But, imagine a case when the derived class does not contain the overloaded method which it does not support…. your program may break unexpectedly or you get undesirable results (preventing subtyping related bugs)

So, the direct consequence of not following LSP is: the resulting classes would not confirm to appropriate class hierarchies which in some cases may result in break down of the program execution too if not handled properly.

How to overcome the above problems by following LSP: You further re-factor your class hierarchy as follows,

Vehicle class will be extended by PetrolVehicle Class and DieselVehicle class as follows:


class Vehicle
{

	public void VehicleCost(){

		//code to get you on road cost of the Vehicle
	}
}

class PetrolVehicle extends Vehicle
{

	public void petrolConsumptionPerMile() {

		//Code to get you Petrol consumption per mile by Petrol Vehicle
	}
}

class DieselVehicle extends Vehicle
{
	public void dieselConsumptionPerMile() {

		//Code to get you Diesel consumption per mile by Diesel Vehicle
	}
}

And, then the HONDACivic and CHEVROLETCruze classes should extend the PetrolVehicle and DieselVehicle classes respectively. By doing this, you confirm to the perfect class hierarchy structure and pave way to good programming skills.

Conclusion, By following LSP, you make your class hierarchies more accurate and pave the way for a robust program structure.

You must make sure that the new derived classes just extend without replacing the functionality of old classes. Otherwise the new classes can produce undesired effects when they are used in existing program modules.