The Liskov Substitution Principle in Delphi! I start with the official definition:
Subtype Requirement: 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.
Still here and wondering what that means? Good, I had some trouble understanding this too. 😊
Let us take a more practical approach to LSP: The principle defines that objects of a superclass shall be replaceable with objects of its subclasses without breaking the application. Or, to stay in Delphi terms: If TChild is of a subtype of TParent, then objects of type TParent may be replaced with objects of type TChild, without breaking the logic of the program.
This means that the underlying classes must work in approximately the same way as the parent class. In contrast to the previous SOLID principles we have dealt with, this one is more about the behaviour of classes, and not directly about the structure of these classes. Let’s get started again with an example.
As you can see, this is a very simple Employee class, which we for example could use like this:
So far so good. But let’s say we want to add some functionality to the Manager, as he has to do an appraisal for an employee. Maybe you remember that I’ve mentioned the preference to program against interfaces, not implementation. But for the sake of this example, we’re going to override our TEmployee class to see what the Liskov Substitution principle is, before we refactor this again to interfaces. So, let’s create the following class:
This is just fine, as we added some functionality. We can now simply change our implementation to this:
We changed the implementation of the Manager object to a TManager, and our program still functions as before. Let’s create another class, and now for our CEO:
and the actual implementation:
Our TCEO overrides from the TManager, implements a new procedure ReviewCompany, and overrides the AssignManager, as this won’t make sense to have a manager for a CEO. But now we have a problem. Let’s say we change our implementation to an instance of TCEO:
Although our project still compiles, we now have a problem when we run this program, because we suddenly get an exception on the AssignManager call. As this breaks our project, this is clearly a violation of the LSP. We should be able to change our TParent to TChild without having a side effect on the functionality.
As you see, the implementation of the TCEO class is stricter than its parent class. The Liskov Substitution Principle states that you can implement less restrictive validation rules, but you are not allowed to enforce stricter ones in your child classes. Similar rules apply to the return value of a function. The return value of a function of the child class needs to comply with the same rules as the return value of the function of the parent.
So, how can we solve this?
The first thing is to ask ourselves; is a CEO really an Employee? Sort of; a CEO can have a salary, but to assign a manager won’t make any sense. Let’s try to solve this with the use of interfaces (again, program against interfaces, not implementation). We start with some interfaces:
As you can see, we have an interface for the basic properties of an employee, we have created a specific interface for the manager and the managed employee, and one for the CEO. The implementations of our classes are now as follows:
And finally the changed calls to these classes:
If you take a good look at the implementation, you can spot an error; the last Employee.AssignManager will give a compile error, because the TCEO class doesn’t have the AssignManager function anymore. So, our logic must change, reflecting the actual situation.
And although we do use inheritance with our classes, we still program against interfaces, as you can see in another code example below:
The implementation of our classes is now also compliant to the Liskov Substitution principle, as we can now swap the TManager.Create with a TEmployee.Create without affecting runtime behaviour.
So, to summarise the Liskov Substitution Principle again: If TChild is of a subtype of TParent, then objects of type TParent may be replaced with objects of type TChild, without breaking the logic of the program. Don’t enforce stricter behaviour in your child classes than defines in your parent classes.
This concludes our explanation of the third SOLID principle, the Liskov Substitution Principle.
Thanks for reading!