In short
In short
NoStrengthening the two clauses arepreconditions and weakening post conditions is not at all the same. They These conditions have a different nature regardless of how you enforce them. And
In many case, you could weaken preconditions or strengthen pre conditions independently, while both can be infringed separately. Numerous countermay result failed tests cases when reusing super-examples invalidate the claim about these beingtype tests for the same thingsub-type.
More details
On LSP, contracts and exceptions (the theory)More details
On LSP and contracts
LSP is about promises made in a contract and not about implementation and exceptions. The preconditions and post conditions do not even need to be implemented by the type and its subtype. Therefore, here a very important distinction:
- preconditions is about conditions that need to be fulfilled before the an operation is performed. If you call the operation but these conditionswhen they are not met, it doesn't mean that you have to throw exceptions. It just means that the promises of that operation (invariants, post conditions) are notno longer guaranteed.
- postconditions is about conditions thatwhat must be guaranteed after the operation is performed, provided the preconditions were initially met. If they are not fulfilled, there's something wrong in the code.
Contracts are not about exception (and vice-versa)
AddingThe pre-/post-conditions do not need to be implemented by the type and its subtype. Adding checks and exceptions inside(inside the type'sthe operations, or outside, in the code that uses the type, before/after calling them) is defensive programming. It helps sometimes to deduce the contract, but not always. For example, whenever there is a divide in some formula, you may find preconditions that must be fulfilled to avoid the divide by zero to happen, even if you don't explicitly throw.
By the way, depending how you design and document contracts, an exception could evenThrowing may be a promised behavior of the contract insteadsign of a failed pre/post-condition(). See forBut an exception could as well be a promised behavior of the contract. For example the following contract that would make your subtype LSP compliant without changing anything in the code:
Task::Close() ensures that the task is in a closed state if closing is possible or that an exception TaskCannotBeClosed is thrown.
Task::Close()
ensures that the task is in a closed state if closing is possible or that an exceptionTaskCannotBeClosed
is thrown.
Hypothetical counter example (the practice)
But enough theory. NowIn this case, in your Task
no exception is thrown not because of a counter-exampledifferent contract but because simpler tasks can alway be closed and hence, that uses exceptionsno need to throw. And throwing for pre and post conditions like you didProjectTask
is no longer a strengthening of preconditions. Take
Example with independent pre and post conditions.
Take the following pseudo-code, which has more distinctunrelated pre and post conditions. Please ignore all the other design and style issues (I would of course design this very differently, it's just to illustrate the case):
Here aYou would have an LSP infringement that strengthensby strengthening the precondition but leaves post condition unchanged:if you would have a TransactionDangerousGoods that has a precondition that customer is 18+. Absolutely no impact on postcondition.
class TransactionDangerousGoods extends Transaction {
Money sellProduct (Customer c, Product p, Quantity q) {
if (c.age()<18) // precondition: Customer is aged 18+
throw CustomerTooYoung;
... do something more specific
// keep same postcondition check as before
}
...
}
Here anotherYou would have an LSP infringement on post condition only and that completely misuses the design:
class Takeback extends Transaction {
Money sellProduct (Customer c, Product p, Quantity q) {
// no precondition, weakens precondition, is ok.
... do something to take the product and reimburse customer
// no postcondition, weakens postcondition ;
return valueCashedIn ; // negative as it is a reimbursement
}
...
}
Hence-condition, we see that strengthening precondition and weakening post condition can in many cases be very differentif you would have a Takeback class as subtype of Transaction, if the tackeback would misuse the sellProduct()
to return the reimbursed amount as negative value.