Huzaifa Rasheed

Huzaifa Rasheed

Software Engineer

Blogs

Is The Liskov Substitution Principle Really Useful?

Posted on March 29, 2021

Repost of https://dev.to/rhuzaifa/is-the-liskov-substitution-principle-really-useful-3oce

Liskov Substitution is part of SOLID Design. SOLID?

SOLID are 5 software development principles or guidelines based on Object-Oriented design making it easier for you to make your projects scalable and maintainable.

Think of them as best practices.

Now What is Liskov Substitution

You see the L in SOLID stands for this principle.

It says

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. Barbara Liskov

Honestly, Too scientific.

In simple terms

Replacing an instance of a class with its child class should not produce any negative side effects or broken codebase.

Meaning

✔️ Can use the subclass of a parent class just the same as using the parent class without breaking anything.
✔️ Subclasses can modify/override parent class methods.
❌ Subclasses can modify the parent’s method signature like arguments, return type, and exceptions.
❌ Subclasses can define a new function not present in the parent class.
❌ Parent class can be modified.

Why do this?

The Goal of this principle is to basically prevent our old codebase from breaking due to new code. This is also in line with the Single Responsibility and the Open Close Principle.

We will use a simple example for explanation.

A Simple Use Case

The following example violates the rule.

class Animal{
	function eat(){
		// common functionality
		return "Eating Now"	// return type string
	}

	function sleep(){
		// common functionality
		return "I am sleeping"	// return type string
	}
}

class Cat extends Animal{
	function eat(){
		// ... cat specific code
		return "Meow, whatever human"	// return type string
	}

	function sleep(){
		// ... cat specific code

		//  voilating LSP: parnet sleep() does not return boolean
		return true 
	}
}

class Dog extends Animal{
	function eat(){
		// ... dog specific code
		return "Woof, It was tasty."	// return type string
	}

	function sleep(){
		// ... dog specific code
		
		//  voilating LSP: parent sleep() doesn't use Error Exception
		throw Error('I just slept') 
	}
}

With the Liskov Substitution Principle, we would modify our code as follow

class Animal{
	function eat(){
		// common functionality
		return "Eating Now"	// return type string
	}

	function sleep(){
		// common functionality
		return "I am sleeping"	// return type string
	}
}

class Cat extends Animal{
	function eat(){
		// ... cat specific code
		return "Meow, whatever human"	// return type string
	}

	function sleep(){
		// ... cat specific code
		return "I am already sleeping"	// return type string
	}
}

class Dog extends Animal{
	function eat(){
		// ... dog specific code
		return "Woof, It was actually tasty."	// return type string
	}

	function sleep(){
		// ... dog specific code
		return "Zzzzzzzz"	// return type string
	}
}

With this approach, we can swap parent and child classes without breaking the code.

So Is it Helpful?

It is in most cases, but there are those cases where you might want to add some more that does not quite fit in like the Birds example below

class Bird{
    function fly(){}
}

class Duck extends Bird{}

class Ostrich extends Bird{} // Duck can fly but ostrich cant:

So yeah, it really depends. If it’s getting over-complicated/over-engineered or is not making sense(like bird example) then it’s best to do your own thing.

Tip

It’s easy to extend old code with new code. You just have to make a new class and extend it with parent/base class without the fear of breaking the already working code. We also get this benefit from Dependency Inversion principle.


So how do you see this? Do you think it’s really useful? Be sure to tell me your opinion in the comments.