This is the 4th post in the series about TDD and its relationships with the SOLID principles. Here are the links to the other parts:
Part 4 – LSP (this post)
The LSP (Liskov Substitution Principle)
The principle (originally stated by Barbara Liskov in a more academic fashion), states that:
FUNCTIONS THAT USE REFERENCES TO BASE CLASSES MUST BE ABLE TO USE OBJECTS OF DERIVED CLASSES WITHOUT KNOWING IT.
Note that the subject of this principle is the classes and not the functions that use them. In other words, it tells you how you should write your derived classes such that any function (that anyone can write, without knowing your implementation) can work with it correctly, as long as it works correctly with the base class.
Your clients should never need to determine the type of your object (and then cast it) in order to be able to use it correctly. If it can work correctly with the base class, it should work with your derived class as is. Therefore, any use of casting, or the ‘is’ and ‘as’ keywords is a “smell” of a violation of LSP.
However, there are more subtle implications to this principle that are not reflected by casting or type checking. These implications are related to the behavior of the class, and not only to its identity. As we’ve learned in the previous post, the OCP helps us keeping backward compatibility. However, LSP tells us that you can’t just override the base class however you like – you must also retain its semantics and expected behavior.
For example, if the base class has a method that returns a string which is never null, or has a specific pattern that the client can rely on, then the derived method must also return a string in the same pattern, so that the client shouldn’t need to be changed.
Here’s another example: a Stack class is expected to have its IsEmpty property be true after calling Pop() the same number of times Push(…) was called. If you create a derived class that has a different behavior (e.g. Pop removes all the consecutive elements having the same value) then you’re likely to break your clients assumptions and violate the LSP.
LSP and Code Contracts
LSP lends itself very naturally to Code Contracts. If you define Code Contracts on your interfaces and base classes, then you’re pretty safe from violating LSP. Just define the pre-conditions, post-conditions and invariants of your base class or interface, and Code Contracts will let you know if you violated anything in the derived class.
LSP and TDD
If you’re writing a new derived class, then you should also derive your test class and validate that everything that was correct on the base class is also true for the derived class.