It’s time of next part of my SOLID series. It is one but last and it’s going to be about Liskov Substitution Principle. The principle states:
If a type ‘T’ is a sub-type of type ‘S’ then objects of type ‘S’ must be fully replaceable by objects of type ‘T’.
This means that if any class is inherited from a superior class then it must be possible for any object of superior class to be replaced by any object of a sub-class without introducing any unwanted behaviour of the code.
The simplest example could be class Rectangle and a sub-class Square. Rectangle has two properties: Height and Width – with getters and setters. Square is derived from Rectangle but its properties are written in that way that changing one dimension also enforces change the other dimension. Now, if we run the below code:
Rectangle rect = new Square();
rect.Height = 5;
rect.Width = 10;
int area = rect.Height * rect.Width
The area of a rectangle 5×10 should obviously be 50. However, result given by our code will be 100 which is doubtlessly incorrect (more on this example here).
Now, the question: how to follow the principle? There are some conditions your code must meet to ensure that the LSP is followed.
Contra-variance of arguments in sub-type methods, co-variance of types returned by sub-type methods: this means that (a): overridden methods in sub-class must accept more generic arguments, and (b): types returned by those methods must be more precise. However, C# does not allow for that so it is necessary to stick with return types and arguments defined in base method’s signature.
No new exceptions thrown in sub-class unless exception types are derived from exception types in base class.
Pre-conditions for a sub-type method are not stronger than for counterpart method in base class: this means that conditions that must be met to successfully invoke a method must be less or equally conservative in sub-class than in base class.
Post-conditions for a sub-type method are not weaker than for counterpart method in base class: this means sub-class method must be more or equally conservative on its results than a counterpart method in base class.
Invariants from base class must be kept in sub-class: his means that if a certain method in a base class does not inflict a change in one property, the same property cannot be changed by counterpart method in sub-class.
Pre-conditions and post-conditions are explained in a clear and simple way in the video below around 42:17 (or click here).
History constraint: this means that what’s immutable in base class must stay immutable in sub-class and what’s mutable in base class must stay mutable in sub-class. So, (a) the sub-class cannot introduce methods that allow change of what’s unchangeable in base class, and (b) if a base class provides ways to modify a member within itself it must be possible to change that in sub-class with use of those ways.
And that’s it. I have to admit that so far the L was the most mind-cracking part of SOLID. I hope that my explanations are clear enough for you. Apologies for no examples this time but I find those given in the video above are better than those I would invent. So, thanks for your time and good luck!
Links to resources used to write this post: