The Liskov Substitution Principle. Commonly stated as: http://en.wikipedia.org/wiki/Liskov_substitution_principle
In a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may substitute objects of type T) without altering any of the desirable properties of that program (correctness, task performed, etc.)
Wow. Surprisingly difficult to understand considering how short and precise a definition it is. So what does it mean in practical terms and what are the usages and benefits of the principle?
Well, credit where it’s due, let us first mention a little history. The principle was identified by Barbara Liskov (http://en.wikipedia.org/wiki/Barbara_Liskov) around 1987. Then more formally defined in conjunction with Jeannette Wing in 1994 (http://en.wikipedia.org/wiki/Jeannette_Wing) to the below definition:
Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.
Before going any further, it is worth mentioning that you need to understand Polymorphism before you can begin on the LSP. If you’re not familiar of comfortable with Polymorphism then please read the following article first: Polymorphism.
So, using some real world examples to try and explain the concept….
Example 1, renting a car
Imagine someone goes to rent a car. Their driving licence allows them to drive a car with either manual or automatic gears. So their requirement to "drive" is simply a "car". Exactly which make and model of car they end up driving is not determined by their driving licence.
If however a second person comes to rent a car and their driving licence only allows them to drive one with automatic gears, then their requirement to "drive" is more specifically an "automatic car". That more explicitly defined requirement will result in a smaller list of car makes and models to choose from, but beyond the "automatic gears" requirement, they still have free choice.
The car will still be used for the same purpose, driven to the same destination, fulfil the same role and work in the same way.
Example 2, eating at a restaurant
A couple decide to dine out one evening at a restaurant. One of them is a vegetarian and the other is a vegan.
With a single sentence, this couple can already be represented by a hierarchy of objects and sub-objects:
Omnivore <- Vegetarian <- Vegan
All three classes of diner will still use the restaurant in the same way, order from a single waiter / waitress, eat the food in the same way, pay the bill and leave. The chef preparing the meals does not know or care that one diner is a vegetarian and one is a vegan. They simply prepare the chosen meals according to the ingredients listed on the menu.
Okay, so two examples of where we have numerous sub-types of object and yet they are both treated the same by the services they consume and the actions that they perform.
If a driver had asked to hire a tank, or a horse attempted to dine at a restaurant, then neither of those object types are sub-types of the ones used in the previous examples, so problems will occur.
The aim of the Liskov Substitution Principle is to ensure code is reusable with as much predictability as possible. This makes the resulting code more generic and only as strictly defined as it needs to be.
If you had a vegetarian restaurant that only served vegetarian meals, you would not prevent meat eaters from dining there, you would simply provide fewer menu choices than they might be used to.
When writing code one of the best ways to embrace LSP is to mentally abstract the operation before writing the code. So instead of:
void ProcessDropDownListControlSelectedValue(DropDownList control);
We can consider the inheritance tree of the 'DropDownList' class, realise that what we are trying to achieve is not strictly tied to that specific class, and is in fact dependent on the base 'ListControl' (http://msdn.microsoft.com/en-us/library/System.Web.UI.WebControls.ListControl(v=vs.110).aspx) class. So we change our method to instead accept that class:
void ProcessListControlSelectedValue(ListControl control);
The functionality within our class does not change. However simply by changing the accepted argument to a less specific sub-class, our code now also supports: BulletedList, CheckBoxList, ListBox, RadioButtonList. This is where the LSP provides real code benefits on a regular basis.
Learning a good habit
So understanding the principle and benefit is only part of the process of writing better code. The most significant benefit is of course putting the technique into practice. This can be difficult when working with complex libraries of types and sub-types. Knowing which type of class to aim for can take a while to learn.
One tool that helped me get into the habit of using the least strictly defined type is a Visual Studio plug-in called: ReSharper (http://www.jetbrains.com/resharper/). Like intellisense and syntax highlighting, the plug-in analyses the code as you write it and suggests improvements as you go along. It’s like having a virtual mentor sat with you as you write.
Below are some external links that should be useful:
- Barbara Liskov invented the language CLU (http://en.wikipedia.org/wiki/CLU_(programming_language)), which is the name of one of the antagonists in Tron Legacy (http://tron.wikia.com/wiki/Clu).