The LSP explained – Architectural Principles

The LSP came from Barbara Liskov at the end of the ‘80s and was revisited during the ‘90s by both Liskov and Jeannette Wing to create the principle that we know and use today. It is also similar to Design by contract, by Bertrand Meyer.Next, let’s look at the formal subtype requirement definition:

Let

be a property provable about objects x of type T. Then,

should be true for objects y of type S, where S is a subtype of T.

In simpler words, if S is a subtype of T, we can replace objects of type T with objects of type S without changing any of the expected behaviors of the program (correctness).The LSP adds the following signature requirements:

The first two rules are tough to violate without effort in C#.

Throwing a new type of exception in subtypes is also considered changing behaviors. You can, however, throw subtyped exceptions in subtypes because the existing consumers can handle them.

The LSP also adds the following behavioral conditions:

ConditionsExamples
Any precondition implemented in a supertype should yield the same outcome in its subtypes, but subtypes can be less strict about it, never more.If a supertype validates that an argument cannot be null , the subtype could remove that validation but not add stricter validation rules.
Any postcondition implemented in a supertype should yield the same outcome in its subtypes, but subtypes can be more strict about it, never less.If the supertype never returns null, the subtype should not return null either or risk breaking the consumers of the object that are not testing for null. If the supertype does not guarantee the returned value cannot be null, then a subtype could decide never to return null, making both instances interchangeable.
Subtypes must preserve the invariance of the supertype.A subtype must pass all the tests written for the supertype, so there is no variance between them (they don’t vary/they react the same).
The history constraint dictates that what happens in the supertype must still occur in the subtype, and you can’t change this.A subtype can add new properties (state) and methods (behaviors). A subtype must not modify the supertype state in any new way.

 Table 3.1: LSP behavioral conditions

OK, at this point, you are right to feel that this is rather complex. Yet, rest assured that this is the less important of those principles because we are moving as far as we can from inheritance, so the LSP should not apply often.

We can summarize the LSP to:

In your subtypes, add new behaviors and states; don’t change existing ones.

In a nutshell, applying the LSP allows us to swap an instance of a class for one of its subclasses without breaking anything.To make a LEGO® analogy: LSP is like swapping a 4×2 block with a 4×2 block with a sticker on it: neither the structure’s structural integrity nor the block’s role changed; the new block only has a new sticker state.

You may also like