#begin
This is the third blog in the series about the SOLID principles of Object Oriented Design. It is all about the L in SOLID, the Liskov Substitution Principle. If you missed the first two blogs in this series, you can read them here and here.
The Liskov Substitution Principle
From my understanding of the Liskov Substitution Principle (LSP), I expect this to be one of the shorter blogs in the serie. But let’s see what information I can find about the LSP.
As with the Open-Closed principle I discussed in the previous blog, this principle is not from Uncle Bob personally either. The LSP was originally coined by barbara Liskov in 1988. She is an American computer scientist and currently works at MIT. She was one of the first women who got a doctorate in computer science an won a Turing award for developing the LSP.
But what exactly does it say;
Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a sub-type of T. [1]
What this essentially means is the following; if for each object of x of type S there is an object y of type T such that for all programs P defined in terms of type T, the behaviour of P is unchanged when x is substituted for y then S is a sub-type of T.
And in layman’s terms; when you design a program around abstract classes or interfaces, you should be able to feed it with concrete implementations of these classes or interfaces and it should work. You should not have to typecast abstract types or interfaces to derived classes. By for example using ‘.instranceof()’ in Java or the safe cast ‘is’ in C#. Simple polymorphic dispatch should be enough.
Some simple example would be the following:
The simple diagram above shows a base class called Vehicle and some derived classes for a Car, Scooter and Motorcycle. This design conforms with the LSP because the behaviour of the application does not depend on which of the sub-types it uses. All three sub-types are substitutable for the Vehicle.
The square / rectangle violation
On of the most canonical example of a violation of the LSP is the shape problem and more specifically, square and rectangle problem. Uncle bob always uses this in his lectures about the LSP and is also the prime example in his book Clean Architecture [2]. I think this is a very good way of explaining how not complying with the LSP can be a big problem because any programmer has probably done some assignment in school or tutorial that involves drawing shapes on the screen. So they most likely ran into this specific issue sometime during their studies.
In this example the Square is not a valid sub-type of Rectangle because the height and width of the rectangle are independently mutable; the height and width of the square must change together. Imagine some unit-test that creates a shape and sets the height to 10px and the width 5px and then test it’s area in the test. If the shape is a rectangle the area would be 50px, but if it were a square it would be 25px.
The only way to defend against this kind of violation of the LSP is to add custom mechanisms and type checking in the code that detects the type of the shape and handle cases correctly. Since this behaviour depends on sub-types, rather than base-types, it is a clear violation of the LSP. Now I think that everyone has done what I describe above, me included. Another issue that arises when the LSP is violated is that these custom type checks will spread around the code base, so there is more affected area’s, not just one.
In one of Uncle bob’s lectures, this one, he says that confusion in regard to the LSP arose from the AI movement that was working on knowledge nets where they would use jargon like square ‘is a’ rectangle, or car ‘has’ headlights. The mistake made here is that they assume that the class rectangle and square actually represent the shapes, but they do not, the class rectangle and square are pieces of code. The representatives are not the things they represent and they don’t share the same relationships! Think like it this way, when you lawyer up for a divorce for example, you and you spouse will have a lawyer representing yourselves, but they are in fact not you, and certainly does not inherit anything from you, and do not share the relationship you and your spouse have/had.
Impact on Software Architecture
From the definition of the LSP it is easy to expect that the LSP is a simple guide on inheritance. Over the years the LSP has evolved into a broader principle for software design of interfaces and its implementations.
The word interface means many things now, not just simply a type definition. An interface in the sense of software architecture might be some REST API. If you would develop a software product that is part of a larger package it would be really nice if all the API endpoints behave the same and you could potentially swap them out. So the LSP has morphed into a principle that is applicable to anything (small or large) that must have a well defined interface.
Again, there is a nice example in the Clean Architecture book:
REST API violation
In the book, Uncle Bob gives the example of some system that aggregates data and dispatch services for taxi companies. Customers are able to go to this website and find an appropriate taxi regardless of the company. Once the user has made choice, the system will dispatch the taxi using a REST API.
Now imagine that the URI for the restful API is defined by data that is in the database.
Like: foobarcab.com/driver/ruben
The system will then append the address information to the url and make it look something like this:
foobarcab.com/driver/ruben
/pickupaddress/awesome street 1337
/pickuptime/2000
/destination/HamersoftHQ
This means that the dispatch services for all the taxi companies have to conform to this style of RESTful service. They must treat the driver, pickupaddress, pickuptime and destination exactly the same. If they don’t we will run into issues.
Now, suppose that the noob-taxi company hired some programmer’s who did not read the spec thoroughly and abbreviated the destination field to dest. The noobs at the noob-taxi company appear to be very stubborn and they are not going to change the restful service.
Obviously, we need to account for this now and we need to add a special case for this. A very simple way of doing this would be to add some if-statement in the code that checks if the URI starts with “noob-taxi.com”. If it does, we can use the abbreviated dest for destination.
However, no sane software architect, or developer would allow such check to be added to the system because this is such an ad-hoc solution it will spread around the code like wildfire. Checks like these are the root cause of many mysterious errors and bugs.
How would we solve this in a more acceptable form? Well, Uncle Bob suggests by making some kind of URI to dispatch format table like below:
URI | Dispatch format |
noob-taxi.com | /pickupaddress/%s/pickuptime/%s/dest/%s |
*.* | /pickupaddress/%s/pickuptime/%s/destination/%s |
This seems like such an overkill solution but it is probably the most flexible and less confusing solution. Things like this really happen when the LSP is violated and architects or programmers have to put in overly complex solutions in the code to cope with it.
The LSP should really be extended to the level of software architecture to allow for robustness and stability.
Impact on other paradigms
The impact of the LSP on the functional paradigm is pretty straightforward and also highly adopted. In functional languages it is common practice to program with generics and polymorphic types and functions. In functional programing there are these types called Functors which simply means it is a function from category/type a to b, and more specifically a contravariant functor which “support polymorphism”.
Functional programming is all about function composition and using small parts (data or functions) for building larger pieces. The LSP is rooted at the very core of the functional paradigm, because without it, you would not get far in this domain.
Additionally, the same rules for the LSP in regard to software architecture in a functional context apply. When you expose public API’s of your functional libraries / components you want them to be as crisp and clear as you can. Try to keep the API’s similar in the way they can be used and the stability will most likely increase while the complexity and weird conditions will lower.
Conclusion
Again, a fun blog to write, although not the longest. I did not want to dive to deeply in the concept of functors and category theory because I do not want to open that can of worms in a blog about SOLID. This probably deserves it’s own blog, series more likely.
The LSP proves to be a very important concept in both the object oriented and functional programming paradigms. Programming complex systems without conforming to the LSP will surely get you into a lot of problems with conditional checks for edge-cases and such. You really want to avoid this and allow for correct polymorphic dispatch. It will make your life, and your colleagues’, a lot easier.
#end
01010010 01110101 01100010 01100101 01101110
References
[1] LSP definition – https://en.wikipedia.org/wiki/Liskov_substitution_principle
[2] Clean Architecture – https://www.oreilly.com/library/view/clean-architecture-a/9780134494272/
Recent Comments