Skip to main content

Liskov Substitution Principle

Introduction

Job Promotion Icon

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 ϕ(x)\phi(x) be a property provable about objects xx of type TT. Then ϕ(y)\phi(y) should be true for objects yy of type SS where SS is a subtype of TT.
-- Barbara Liskov

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.

Image Credits

Job-promotion icons created by Freepik - Flaticon