Yes it breaks LSP. Specifically the setAge
method is a problem. The solution IMO is to remove the method or change its postcondition.
By that I mean, the implied postcondition of setAge(age: number)
is assert(this.age == age)
. If you change that, then you can adjust the precondition so that both Person and Employee can keep the method.
For Person:
setAge (age: number) {
assert(true); // precondition
if (age >= 0) {
this.age = age;
}
assert(this.age >= 0); // post-condition
}
For Employee:
setAge (age: number) {
assert(true); // precondition
if (age >= 18) {
this.age = age;
}
assert(this.age >= 18); // post-condition
}
With the above I have:
- Not strengthened the precondition (They are both the same.)
- Not weakend the postcondition (in fact I have strengthend it.)
- Preserved the invariant (the implied invariant is
age >= 0
You will notice that the real change in the above is that setAge
is no longer a demand, rather it is a suggestion and that makes all the difference.
In general, the way to ensure that your classes conform to LSP is to give them methods where you tell them about events that happened outside the object rather than telling them to perform actions inside. This is why in many systems you see methods called something like xDidHappen
instead of doX
.
When you tell an object that x happened, you are leaving it up to the object to decide what to do in response to that. This is exactly what objects are supposed to do. When you tell an object to "do X" (or in this case "set X") you are removing agency from the object. It is no longer in control of its own state. Something outside of the object now is calling all the shots. That's not OO; That's imperative programming in OO clothing.