You can't decide whether some code violates the LSP by the code itself. You need to know the contract that each method is to fulfill.
In the example, there is no explicit contract given, so we have to guess what the intended contract of the Close()
method might be.
Looking at the base class implementation of te Close() method, the only effect of that method is, that afterwards the Status
is Status.Closed
. My best guess of a contract for this method reads:
Do whatever is necessary to make the Status
become Status.Closed
.
But that's just a plausible guess. Nobody can be sure about that if it isn't explicitly written down.
Let's take my guess for granted.
Does the overridden Close()
method also fulfill that contract? There are two possibilities that after running this method we have Status.Closed
:
- We already had
Status.Closed
before calling the method.
- We had
Status.Started
. Then we call the base implementation, setting the field to Status.Closed
.
- In all other cases we end up with a different status.
If Status
only has the two possible values Closed
and Started
(e.g. a 2-value enum), everything is fine, there's no LSP violation, because we always get Status.Closed
after the Close()
method.
But probably there are more possible Status
values, ending up in a Status
not being Status.Closed
, thus violating the contract.
The OP asked about the famous sentence "wherever I am using the base class, its derived class can be used".
So I'd like to elaborate on that.
I read it as "wherever I am using the base class within its contract, its derived class can be used, without violating that contract.
So it's not only about not producing compile errors or running without throwing errors, it's about doing what the contract demands.
And it only applies to situations where I ask the class to do something that's within its intended range of operations. So we need not care about abuse situations (e.g. where preconditions aren't met).
After re-reading your question, I think I should add a paragraph on polymorphism in that context.
Polymorphism means that for instances of different classes, the same method call results in different implementations being run. So polymorphism technically allows you to override our Close()
method with one that instead e.g. opens a stream. Technically, that's possible, but it's a bad use of polymorphism. And one principle about good and bad uses of polymorphism is the LSP.
Task Close
method, whereas the overriddenProjectTask
implementation adds the precondition that status may NOT be Started, i.e. strengthening from No contract to some contract precondition. You don't have any contracts in your example. However there is bad OO practice - by making Close virtual, subclasses can bypass any contracts it may have contained, and by making Status set public, any external code can change state and bypass all contracts and rules anyway.