DEV Community

Cover image for Is The Liskov Substitution Principle Really Useful?
Huzaifa Rasheed
Huzaifa Rasheed

Posted on • Updated on

Is The Liskov Substitution Principle Really Useful?

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') 
    }
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (3)

Collapse
 
jexperton profile image
Jonathan Experton • Edited

I might misunderstand the Liskov substitution principle but here's how I would read it:

Replacing an instance of a class with any other class that extends the same parent class should not produce any negative side effects or broken codebase.

In other words:

Classes that share the same interfaces (contracts) should be interchangeable.

This is a condition to polymorphism and therefore a condition to the open-close principle and a way to avoid writing rigid softwares.

It also relates to the Interface Segregation principle as you don't want to bloat your classes with useless contracts that just prevent the interchangeabilility.

I admit the original definition is cryptic to me and not very useful.

Collapse
 
miguelmj profile image
MiguelMJ

As you pointed out, SOLID principles are meant to make your object oriented code scalable and maintainable. In my opinion, good practices have their place and should be used by default. However, there may be cases where a generally considered good practice just hinder you.
You have to know well what you are doing, but there might be code that doesn't need scalability, because is follows a rigid design that simply benefits from some characteristics of OOP. If you know (and I mean really know) the consequences of breaking the rules and are OK with it, then "the best practices" shouldn't be an obstacle.

Collapse
 
rhuzaifa profile image
Huzaifa Rasheed

Good Explanation, Miguel. I agree with that, thus left it open for the reader to decide.