Liskov Substitution Principle (LSP)
The Liskov Substitution Principle (LSP) is a concept in object-oriented programming that states that if a program is using a base class, it should be able to use any of its subclasses without the program knowing it. In other words, subclasses should be substitutable for their base classes without affecting the correctness of the program.
The principle is named after Barbara Liskov, who introduced it during her 1987 keynote address titled "Data Abstraction and Hierarchy". This principle is also part of the five principles of SOLID in object-oriented design and programming:
The Liskov Substitution Principle is formally defined as: "If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T."
In simpler terms, this means that any method that uses a reference to the base class must be able to use any of its subclasses indistinguishably, without even knowing it, thus ensuring the same behavior. Violations of the LSP can often lead to strange and unpredictable behavior in software.
For example, consider a Bird
class with a method fly()
. If we have a Penguin
subclass of Bird
, it wouldn't be correct to have the fly()
method, since penguins can't fly. If we violate LSP and implement a Penguin
's fly()
method to throw an exception or do nothing, any code which expects a Bird
to be able to fly()
would fail if it's given a Penguin
. This is why adhering to LSP is important to maintain the integrity and predictability of an object-oriented system.
LSP is about creating subclasses that can truly stand in for their base classes. It's a specification of a behavioral notion of subtyping. Let's take a look at a more concrete example.
Assume we have a class named Rectangle
with methods setWidth(double width)
, setHeight(double height)
, getWidth()
, and getHeight()
. It also has a method getArea()
which returns the product of height and width. This makes perfect sense for a rectangle.
Now, consider we want to add a class Square
. A square is a special type of rectangle, so it makes sense to make Square
a subclass of Rectangle
. However, a square has a special property: its width is always equal to its height.
How would we implement setWidth()
and setHeight()
in Square
? If we were to use the superclass methods, we could set width and height independently, which would violate the rule of a square. So, we might decide to override setWidth()
and setHeight()
in Square
to always change both width and height to the new value.
But now consider a piece of code that uses a Rectangle
object and changes the width expecting the height to remain unchanged (which is perfectly reasonable for a rectangle). If we were to pass a Square
object into this code, it wouldn't behave correctly because setting the width also changes the height in a Square
. Thus, we've violated the Liskov Substitution Principle.
The Liskov Substitution Principle forces us to make sure all derived or child classes should be substitutable for their base or parent class. A violation of this principle can introduce bugs when we attempt to use a subclass object in a place where a superclass object is expected.
So in the example above, to adhere to the LSP, Square
should not be a subclass of Rectangle
. Instead, both Square
and Rectangle
could be subclasses of a more generic class, perhaps named Polygon
or Shape
.
In practical terms, following LSP allows us to add more and more subclasses into a software system without fear of breaking existing functionality. We can confidently use objects of the superclass and know that any object of a subclass can stand in and behave correctly.