The Liskov Substitution Principle states that in an inheritance, a parent class should be substitutable for its child class without any problem.
A parent class should be substitutable for its child class without any problem.
This is because in inheritance, the child class should be able to do everything the parent class can do, delivering the same or even improved results.
Getting into more detail about the definition of the class, the principle also implies that the child class's methods should have the same amount of arguments and the arguments type and return type should be of the same type of the parent class that they override.
The objective behind this principle is to strengthen consistency so that parent and children classes can be used in the same way without any errors.
Example
Imagine we have a parent class Animal
and it has two children, Dog
and Cat
. Each animal has its unique sound, dogs can bark and cats can meow. At first, our class declarations will look like this:
class Animal {
bark() {
return 'This is a bark sound!';
}
meow() {
return 'This is meow sound!';
}
}
class Dog extends Animal {
bark() {
return 'Woof woof';
}
meow() {
return new Error(`Dogs can't meow`);
}
}
class Cat extends Animal {
bark() {
return new Error(`Cats can't bark`);
}
meow() {
return 'Meow meow';
}
}
const dog = new Animal();
const cat = new Animal();
// The following method calls will print 'This is an bark sound!'
console.log(dog.bark());
console.log(cat.bark());
// The following method calls will print 'This is a meow sound!'
console.log(dog.meow());
console.log(cat.meow());
Both bark
method calls will print "This is a bark sound!" and both meow
method calls will print "This is a meow sound!". Now, if we were to substitute the parent class with the corresponding children, we would have a problem and a violation of the Liskov Substitution Principle. This is because dogs can't meow and cats can't bark, or in other words, they can't perform all the actions their parent can. Also, on the children class, some methods are returning a different type compared to what their parent returns. The code for this violation would be the following:
// Change Animal to the corresponding children
const dog = new Dog();
const cat = new Cat();
// The dog will bark and the cat will throw an error
console.log(dog.bark());
console.log(cat.bark());
// The cat will meow and the dog will throw an error
console.log(dog.meow());
console.log(cat.meow());
To fix this violation of the principle, we will create a more generic method for the animal sounds instead of specific methods for each animal sound. And also, we will make sure that every method returns the same type. The refactoring of this code will be the following:
class Animal {
makeSound() {
return 'This is an animal sound';
}
}
class Dog extends Animal {
makeSound() {
return 'Woof woof';
}
}
class Cat extends Animal {
makeSound() {
return 'Meow meow';
}
}
let dog = new Animal();
let cat = new Animal();
// These method calls will print 'This is an animal sound!'
console.log(dog.makeSound());
console.log(cat.makeSound());
dog = new Dog();
cat = new Cat();
// The dog will bark and the cat will meow
console.log(dog.makeSound());
console.log(cat.makeSound());
As you can see, we successfully substituted the parent class with any of their children and the classes were able to handle the requests without a problem, just as the Liskov Substitution Principle states.
Top comments (2)
Thank you, Josué. This was well explained!
Thanks