Abstract
Quote
Objects of a superclass should be replaceable with objects of a subclass without affecting the correctness of the program.
- Not all “Is-a” Relationship should be modelled with Inheritance
What is the correctness of the program?
If a program works correctly using objects of a certain type, it should also work correctly if you replace those objects with objects of a subtype (a class that inherits from the original type).
The Liskov Substitution Principle is about ensuring that subclasses behave in a way that’s compatible with their parent classes. This helps prevent unexpected bugs and makes your code more reliable. Remember, with the power of inheritance comes the responsibility to use it wisely.
Fly example
Consider a base class
Bird
with a methodfly()
. If you derive a classPenguin
fromBird
, butPenguin
cannot fly, it would violate the LSP because substituting aBird
with aPenguin
would break thefly()
method’s expected behaviour.The derived class should enhance or maintain the behavior of the base class, but never contradict or weaken it.
Length example
In the Liskov Substitution Principle (LSP), if we have a
Square
class that inherits from aRectangle
class, issues arise when performing certain operations. For example, when we perform thechange width
operation on both objects:
- The
Square
will change both its width and length, as they are always equal.- The
Rectangle
will only change its width, leaving its length unchanged.Later, when we perform the
obtain length
operation:
- The
Square
will return its changed length (which is now equal to its changed width).- The
Rectangle
will return its original, unchanged length.This discrepancy in behavior violates the principle of OOP Compatibility, a key aspect of the LSP. The LSP states that objects of a derived class should be substitutable for objects of their base class without affecting the correctness of the program. In this case, substituting a
Square
for aRectangle
leads to unexpected results, breaking the program’s correctness.
How can we fix this?
Leverage a Common OOP Interface: Abstract the
Square
andRectangle
properties into an interface. Implement thewidth
andlength
in each respective class. This prevents object substitution when obtaining thelength
(which is specific toRectangle
objects), thereby ensuring OOP compatibility.Prevent Inheritance and Method Overriding: Declare the OOP class or individual OOP methods as final. This enhances the ability to reason about program correctness. Noteworthy examples include
java.lang.Math
andjava.lang.String
, which are not inheritable.Unit Testing: Employ assertions to verify the LSP during compilation. You can find an illustrative example here.