Liskov substituition

Liskov Substitution Principal (LSP) -

As per the LSP, functions that use references to base classes must be able to use objects of the derived class without knowing it. In simple words, derived classes must be substitutable for the base class.

In object-oriented design, inheritance is usually described as an "is a" relationship. A common technique of creating objects with like behavior is the use of super- and sub-types. A supertype is defined with some set of characteristics that all of its subtypes then inherit. In turn, subtypes may then choose to override the supertype’s implementation of some behavior, thus allowing for behavior differentiation through polymorphism.

However, it raises the question of what exactly makes one object a subtype of another?

Barbara Liskov proposed an answer to this question, arguing that an object should only be considered a subtype of another object if it is interchangeable with its parent object so far as any interacting function is concerned

But that's not all, for LSP compliance, we need to follow several rules.

We can categorize these rules into 2 groups.

1. Contract rules

2. Variance rules

We will get into both these groups and understand these rules.

Contract Rules :

When we program to interfaces we stick to very loose notion of contract because as per interface we can implement all required methods with returns values and passing parameter types as expected. But to make sure if valid parameter argument values are passed to method(preconditons) and valid values are returned from method(postconditions) should be taken care of that in the class implementing this interface.

Preconditions are nothing but necessary conditions necessary for a method to run reliably and without fault.

Postconditions are nothings but checks required to ensure that valid value is returned from the method.

Now, if we look back at formal definition of LSP it states that :

"If S is subtype of T.

then object of type T may be replaced with object of type S

without breaking the system"


So Contract rules are:

  • Preconditions cannot be strengthened in a subtype: When sub class overrides an existing method that contains preconditons, it should not further strengthen these existing preconditons. If we do then we might breaks the system.

  • Postconditions cannot be weakened in a subtype: It is opposite here because this rule applies to return values and we should not be downgrading or weakningn the existing post conditions. If we do then we might break the system.

  • Invariants must be maintained: This means when we create new subclass it should honor all the data invarients that were in base class. Subclass should not change change base class data. so the conditions that must remain true must be preserved in subtype.

Rememeber, if comply with LSP then any subclass we created should be usable by all existing clients without causing them to fail in unexpected ways.

Variance Rules:

  • There must be contravariance of the method argument in the subtype

  • There must be covariance of the return type in subtype

  • No new exceptions can be thrown by the subtype unless they are part of the existing exception hierarchy.

Covariance and contravariance are terms that are used in different ways in the theory of object-oriented programming.

They derive from a very general idea based on observing what happens when you make a change - the result usually goes in the same direction as the change, i.e. it is covariant, but sometimes it goes in the other direction, i.e. it is contravariant.

The common use of the terms contravariance and covariance in programming has come to mean something a little more specific than in physics and math - but still often related to the inputs and outputs of functions.

The whole idea relates to the hierarchy of types. If type B is derived from type A that is it "inherits" from A or, put another way, A is the base class for B - then it contains every method and property that type A does and more.

In this sense type B is "bigger" than type A.

You can see that the type system provides a way of ordering classes. The idea that B is in some way "bigger" than A is an important idea. Unfortunately it has long been the case that we use the less than symbol to show that a type is derived from another type which is perhaps the wrong way round.

That is if we write A>B then A is "higher" in the type hierarchy than B. In other words B is derived from A.

So as per Wiki:

  • covariant if it preserves the ordering of types (≤), which orders types from more specific to more generic;

  • contravariant if it reverses this ordering;

  • bivariant if both of these apply (i.e., both I<A> ≤ I<B> and I<B> ≤ I<A> at the same time)[1];

  • invariant or nonvariant if neither of these applies.

For example, in C#, if Cat is a subtype of Animal, then:

  • IEnumerable<Cat> is a subtype of IEnumerable<Animal>. The subtyping is preserved because IEnumerable<T> is covariant on T.

  • Action<Animal> is a subtype of Action<Cat>. The subtyping is reversed because Action<T> is contravariant on T.

  • Neither IList<Cat> nor IList<Animal> is a subtype of the other, because IList<T> is invariant on T.

More details on covariance and contravariance can be found here: