It seems to me that this is an example where the violation of LSP arise, in fact, from a prior violation of some other SOLID principle, in this case, the Single Responsibility.
Let's work with the problem at hand: whats Task's responsibility? Its not clear. Lets say it is to ... perform a Task. If that's the case, it should not worry about whatever close has to do. Clients of, lets say..., AbstractTask
interface should only worry about tasks, so every AbstractTask
concrete implementation job should be only to implement its task in a predictable and stable way. LSP rules are for that.
On the other hand, there's a requirement of a task always being able to be closed. But there are tasks that can be closed at any time, there are tasks that that must satisfy some conditions before being ready to be closed. This is kinda similar to the canonical LSP violation example of the Real vs Toy duck:
class Duck{
void quack() = 0 //abstract interface
...
}
class RealDuck() {
void quack() {// say "quack!"}
...
}
class ToyDuck() {
void quack() { if(has_batteries) // say quack}
...
}
At this point I believe that the best choice depends on your problem requirements. If one expects to have a lot of concrete implementations that violates LSP this way I think that the best choice is to use the Strategy Design Pattern. Strategy decouples the client object from the components that implement its behaviour. This way LSP is intact (no strict requirements in derived classes, change of behaviour chosen at runtime) and greater flexibility and reusability is achieved.
But it doesn't seems to be the case of task/projectTask. In this scenario, where few exceptions to the general rule are expected, a simple and effective solution is to just implement SRP and segregate Closeable
from other tasks (e.g., regular tasks implement Closeable
, projectTask doesn't) and making clients that may close tasks to rely upon Closeable
and not upon Tasks. Static checks at compile time guarantee that projectTask will never be used when freely Closeable
objects are required, even if projectTask
has a close()
method itself.
Sounds good? I hope it helps.
public Status Status { get; private set; }
; otherwise theClose()
method can be worked around.Task
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.TaskCloser
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!