A parent class must be able to be substituted by its child classes without breaking the application.
In practice, this helps to organize the idea of inheritance, as it prevents us from extending a parent class only to later remove an already implemented method or do a “throw new Error(‘Not implemented’)”.
Making us much more careful during planning.
THE BIGGEST SYMPTOM OF ERROR
Unfortunately, it is a symptom that appears late, but it is exactly when we are going to make a new implementation.
You realize you violated Liskov when you are going to build a class or subclass and need to purposely throw an error in the implementation of a method.
Exactly because that method shouldn't be there, but it is.
A BAD EXAMPLE
For example, in a delivery system.
In this case, the “Delivery” class should be the parent/base for the other implementations.
But the ‘MotoboyDelivery’ class breaks this.
Code Example:
// BAD: The subclass breaks the parent class contract.
class Delivery {
public calculateShipping(): number {
return 15.0;
}
public getTrackingCode(): string {
return "TRK123456789";
}
}
class MotoboyDelivery extends Delivery {
public calculateShipping(): number {
return 8.0;
}
// ERROR! There is no tracking code.
public getTrackingCode(): string {
throw new Error("Motoboys do not have a tracking code.");
}
}
THE SOLUTION
For those who do not yet know the 'Liskov Substitution Principle', it might seem that fitting in a sequence of 'if's is the solution.
But in reality, the ideal path is to rethink how this abstraction is built.
A good guiding principle is to think that a child class must always be able to take the place of the parent, without breaking the application.
A GOOD EXAMPLE
Still in the delivery system.
‘Delivery’ now has ‘TrackableDelivery’ in the middle of the way.
With this, each “leaf”/edge of the application inherits what makes the most sense and nothing is broken.
Code Example:
interface Delivery {
calculateShipping(): number;
}
interface TrackableDelivery extends Delivery {
getTrackingCode(): string;
}
class CorreiosDelivery implements TrackableDelivery {
public calculateShipping(): number {
return 15.0;
}
public getTrackingCode(): string {
return "BR987654321X";
}
}
class MotoboyDelivery implements Delivery {
public calculateShipping(): number {
return 8.0;
}
}
CONCLUSION
Respecting Liskov guarantees predictability.
Whoever calls your method blindly trusts that the contract will be fulfilled, regardless of the class that is passed under the hood.
This brings more security during the implementation of new features or even in the maintenance of old ones.
My Links
Github: victor-lis-bronzo
Linkedin: victor-lis-bronzo
Portfolio: portfolio.victorlisbronzo.me
Coolest Portfolio: victorlisbronzo.me
Leave your reaction ❤️
And have you ever come across a method violating Liskov in any application?
Top comments (0)