This may or may not be a violation of the LSP.
Seriously. Hear me out.
If you follow the LSP, objects of type ProjectTask
must behave as objects of type Task
are expected to behave.
The problem with your code is that you have not documented how objects of type Task
are expected to behave. You have written code, but no contracts. I'll add a contract for Task.Close
. Depending on the contract I add, the code for ProjectTask.Close
either does or does not follow the LSP.
Given the following contract for Task.Close, the code for ProjectTask.Close
does not follow the LSP:
// Behaviour: Moves the task to the closed state
// and does not throw any Exception.
// Default behaviour: Moves the task to the closed state
// and does not throw any Exception.
public virtual void Close()
{
Status = Status.Closed;
}
Given the following contract for Task.Close, the code for ProjectTask.Close
does follow the LSP:
// Behaviour: Moves the task to the closed status if possible.
// If this is not possible, this method throws an Exception
// and leaves the status unchanged.
// Default behaviour: Moves the task to the closed state
// and does not throw any Exception.
public virtual void Close()
{
Status = Status.Closed;
}
Methods that may be overridden should be documented in two ways:
The "Behaviour" documents what can be relied on by a client who knows the recipient object is a Task
, but doesn't know what class it is a direct instance of. It also tells designers of subclasses which overrides are reasonable and which are not reasonable.
The "Default behaviour" documents what can be relied on by a client who knows that the recipient object is a direct instance of Task
(i.e. what you get if you use new Task()
. It also tells designers of subclasses what behaviour will be inherited if they don't override the method.
Now the following relations should hold:
- If S is a subtype of T, the documented behaviour of S should refine the documented behaviour of T.
- If S is a subtype of (or equal to) T, the behaviour of S's code should refine the documented behaviour of T.
- If S is a subtype of (or equal to) T, the default behaviour of S should refine the documented behaviour of T.
- The actual behaviour of the code for a class should refine its documented default behaviour.
public Status Status { get; private set; }
; otherwise theClose()
method can be worked around. – Job Oct 16 '12 at 20:52Task
don't introduce bizarre incompatibilities in polymorphic code which only knows aboutTask
is a big deal. LSP isn't a whim, but was introduced precisely in order to help maintainability in large systems. – Andres F. Mar 3 '16 at 21:21TaskCloser
process whichclosesAllTasks(tasks)
. This process obviously doesn't attempt to catch exceptions; after all, it's not part of the explicit contract ofTask.Close()
. Now you introduceProjectTask
and suddenly yourTaskCloser
starts throwing (possibly unhandled) exceptions. This is a big deal! – Andres F. Mar 3 '16 at 21:24