Liskov Substitution Principle
Introduction
The Liskov Substitution Principle states that subclasses should be substitutable for the classes from which they were derived. This is known as behavioral subtyping.
The principle is formally stated as follows:
Let be a property provable about objects of type . Then should be true for objects of type where is a subtype of .
-- Barbara Liskov
The Liskov Substitution Principle is named after Barbara Liskov, who was one of the first women in the United States to be awarded a Ph.D. in computer science. She is also known for her pioneering work in designing programming languages and was awarded the Turing Award in 2008.
Example
The formal statement of the Liskov Substitution Principle can be a little hard to understand. Let's look at an example to make it clearer. Here we have an abstract base class for a Bird
with a method fly()
. We also have a single subclass Duck
that implements the fly()
method.
abstract class Bird {
abstract fun fly()
}
class Duck : Bird() {
override fun fly() {
println("Duck is flying")
}
}
This work fine until we introduce a new subclass Penguin
that cannot fly. The Penguin
class will have to override the fly()
method and throw an exception.
class Penguin : Bird() {
override fun fly() {
throw UnsupportedOperationException()
}
}
This violates the Liskov Substitution Principle because the Penguin
class is not substitutable for the Bird
class in every instance. The Penguin
class should not have to override the fly()
method at all.
In order to make this adhere to the Liskov Substitution Principle, we could introduce an intermediate class FlyingBird
that extends Bird
and implements the fly()
method. The Duck
class can then extend FlyingBird
and the Penguin
class can extend Bird
.
abstract class Bird
abstract class FlyingBird : Bird() {
abstract fun fly()
}
class Duck : FlyingBird() {
override fun fly() {
println("Duck is flying")
}
}
class Penguin : Bird()
With this change anywhere that a Bird
is expected, a Penguin
can be used without any issues.
Rules
Violations of the Liskov Substitution principle are usually viewed as a code smell rather than a strict problem that needs to be addressed. Watch for overly generalized classes. You may end up with superclasses where none are needed.
If you can't substitute a superclass with any of its subclasses then you are forced to use if-else or switch statements in order to handle special cases for each subclass. This is a clear violation of the Liskov Substitution Principle.
Liskov Substitution Rules
- Parameter types in a method that you override in a subclass should match or be more abstract than parameter types in the superclass.
- The return type in a method of a subclass should match or be a subtype of the return type in the superclass.
- A method in a subclass shouldn't throw types of exceptions that the base method isn't expected to throw.
- A subclass shouldn't strengthen pre-conditions.
- A subclass shouldn't weaken post-conditions.
- Invariants of a superclass must be preserved.
Conclusion
The Liskov Substitution Principle is a key principle in object-oriented design. It helps to ensure that subclasses can be used in place of their superclasses without any issues. This makes code more flexible and easier to maintain.