Ensure derived classes are substitutable for Base classes with Liskov’s Substitution Principle
This article, the third of the 5-part article on S.O.L.I.D design principles, is about the “L,” Liskov’s substitution principle made famous by Robert C. Martin (Uncle Bob)in his paper, https://web.archive.org/web/20150924054349/http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf. The principle was initially introduced by Barbara Liskov in a 1987 conference keynote address titled Data abstraction and hierarchy. https://dl.acm.org/citation.cfm?id=62141
L — Liskov substitution principle
Contracts, Promises & APIs
The public interface, method of an object is its contract with the rest of the world. That API or contract is that objects promise to the world or the client of what service it provides. In the field of Software engineering, modularisation is based on contracts and APIs. Objects provide some service and expose those via such contracts or APIs. For e.g., the Car object promises its clients that calling the function startEngine() on itself will start the engine. This is canonical. This should never be altered.
When a subclass extends a base class it derives all its interfaces, contracts. This is well known as Inheritance. The contracts associated with the Base class should still be the same in the derived class. The derived classes should not alter the working or execution of the base class contracts by changes introduced in itself. A derived class should add its own unique implementation, but the behavior of the base class contracts should always be honored. So, in the perfect world, a derived class should easily be substitutable for the base class.
In her paper, Barbara Liskov defines LSP as
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 subtype of T.
A general definition would be
Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
Uncle Bob says
Derived classes must be substitutable for their base classes.
And if you are in the mood for a few laughs…
If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck. That is unless you break the Liskov Substitution Principle.
Consider a new requirement of maintaining timesheets for workers in a company. A simple interface for Worker class would be as under. We have discussed the virtues of the abstract interface over Implementation inheritance as seen with OCP. So we go ahead with an abstract interface to help our implementations to be open for modifications.
We now have different categories of workers. People who work on the floor, people in the corner office, people who work offsite, etc. Depending on their seniority & responsibility they work at different times. For example, floor employees work from 9 to 6. Managers work from 8 to 8. Thanks to our Abstract interface for Worker, we can create a specific type of worker implementations as under
As time progresses, machines are taking over! A new category of workers is now introduced. Robots. Android and Bots which work all the time on manual tedious jobs.Untiring & dedicated. Clearly, a robot “is-a” worker and since we are super smart and we have implemented Workers with OCP, we happily define a new implementation known as Robot. The client has completely abstracted from this new kind worker and everything works as normal. Or does it?
As we can see above, Robots work all the time and hence they don't have a clock in time and a clock out time. In his original paper on LSP, Robert. C. Martin makes a nice point about ‘Clients Ruin Everything’ when it comes to contracts. As we discussed above, the public interface of an object is its contract with the outside world. In the case of Worker, clockedInTime(), ClockedOutTime(), timeTakenForLunch() are its interface to the outside world
A client may use this information to make some business decisions. For e.g, how long is an average employee working? Are they spending too much time at lunch? And so on and so forth. And the expectation from the client would be that only employees will adhere to this contract
Thanks to OCP, our Worker Interface can support infinite kinds of a concrete worker but clearly Robot workers are a misfit here. A client expects every employee to fall into one of the two shifts. Robots don't work in shifts. So the contract clockedInTime() & clockedOutTime() is violated by the Robot. We cannot substitute worker.clockedInTime() or worker.clockedOutTime() with robot.clockedInTime() or robot.clockedOutTime(). Another violation happens because robots don't eat lunch!. So timeTakenForLunch() is not implemented in the case of Robots. Sure, we could have an empty implementation or throw an exception or some kind of band-aid solution to this, but it would hide the fact that the error is in our modeling i.e A Robot is not necessarily “is-a” worker. Thus LSP is violated because of the flaw in our design. We simply cannot substitute an instance of the derived class i.e Robot for an instance of the Base class
Liskov’s substitution principle requires a designer to take the perception of the client into account before designing the interface of the class. How will they use it? Will they use it in entirety? Will they replace it with its derivative? If so what kinds of derivatives are possible etc. All derivations of a base class must honor its contracts else its a violation of LSP