Can't weakening preconditions and strengthening postconditions also violate Liskov Substitution Principle?
Asked Answered
C

5

10

Actual precondition of a subtype is created by combining ( using logical OR ) preconditions of a base type and preconditions of a subtype, which makes the resulting precondition less restrictive

Actual postcondition of a subtype is created by combining ( using logical AND ) postconditions of a base type and postconditions of a subtype, which makes the resulting postcondition more restrictive

The following are examples of strengthening preconditions and weakening postconditions, which as a result violate LSP ( Link ):

  1. Assume your base class works with a member int. Now your subtype requires that int to be positive. This is strengthened pre-conditions, and now any code that worked perfectly fine before with negative ints is broken.

  2. Likewise, assume the same scenario, but the base class used to guarantee that the member would be positive after being called. Then the subtype changes the behavior to allow negative ints. Code that works on the object (and assumes that the post-condition is a positive int) is now broken since the post-condition is not upheld.

a) Why isn't it also considered a violation of LSP when overridden method weakens a precondition, since this method could use parameters that are not acceptable to the contracts of the base type. As such, couldn't we claim that contract of the base type was violated and as a result LSP was also violated?

b) Why isn't it also considered a violation of LSP when overridden method strengthens the postcondition, since clients invoking this method will only receive a subset of possible results of the original method. As such, couldn't we claim that contract of the base type was violated and as a result LSP was also violated?

Example:

Base class postcondition guarantees that the return value of a method would be within range 1-10, but then the subtype changes the postcondition to only allow return value to be within the range 2-9. Now code that works on the object returned from this method ( and assumes that the postcondition is within a range 1-10 ) is broken since the postcondition is not upheld.

Commodity answered 8/5, 2013 at 17:42 Comment(0)
T
3

Sorry, but you have a logical error in your considerations.

Base class postcondition guarantees that the return value of a method would be within range 1-10, but then the subtype changes the postcondition to only allow return value to be within the range 2-9.

Since code works within range 1-10 and range 2-9 is actually within range 1-10, strenghtening the postcondition should be never a problem.

Same with weakening the preconditions. Allowing the subtype to accept a greater range is not breaking the base type's behaviour. Since the behaviour is introduced in subtype only and only as a precondition for subtype's methods.

Teetotum answered 22/9, 2015 at 9:22 Comment(0)
U
-1

I think your example doesn't support your point in the following sense.

In the negative int, positive int example, adding negative numbers although it includes more possibilities weakens the guarantee of the post-condition. Your example kind of does the same thing. Although it guarantees a tighter range, it still weakens the guarantee of the post-condition provided by the base class.

The post-condition rule actually says:

This rule says that the subtype method provides more than the supertype method: when it returns everything that the supertype method would provide is assured, and maybe some additional effects as well (Program Development in Java, p177)

Your example doesn't guarantee everything that the supertype guarantees. I guess in your example strengthening would mean that the subtype guarantees that the returning int is in 1-10, furthermore it guarantees that the returning int is in between 2-9, not just the second one.

Unamuno answered 13/6, 2013 at 7:20 Comment(0)
M
-1

If the base-class contract for a method says that invoking it with a negative number will throw an exception, then any legitimate derived class should throw the same exception if its method is passed a negative number. If, however, the base-class contract simply says that a method must not be called with negative numbers without specifying what will happen if it is, then a base class could do do anything it likes without breaking that contract.

While it may seem inelegant to have conditions where the behavior of a class would be unspecified, adding features to a class while remaining upwardly-compatible generally requires changing what had been unspecified behaviors into specified ones. In some cases, an "unspecified" behavior on a class may be "failing to compile", but there's no guarantee that code attempting to use such a member will always fail to compile. For example, if the contract of a class makes no mention of a member "Foo", attempting to use such a member would likely cause a compilation error, but a future version of the class could define Foo without violating its contract.

Morrison answered 29/7, 2013 at 22:8 Comment(0)
M
-1

LSP means you should be able to substitute the base-class with the sub-class for in-specification input values. It doesn't make sense to compare behavior of base-class vs sub-class when you are violating the contract of the base-class in the first-place (you are not dealing with true substitution if you do).

(Violating the contract is unknown behavior, it just happens that the most common approach is to throw an exception.)

Regarding strengthening of the postcondition, I don't see your point, really(?) If the contract is specifying values 1-10, any values between 1-10 is de facto in-specification, also 2-9 or even 3 always(?)

Miyokomizar answered 3/12, 2014 at 22:41 Comment(0)
A
-2

You're perfectly right. Preconditions cannot be weakened. This would also change the behaviour of the base type. For example:

class Base { void method(int x) { /* x: 1-100 allowed else exception  */ } }
class Weak: Base { void method(int x) { /* x: 1-1000  allowed else exception  */ } }
class Strong: Base { void method(int x) { /* x: 1-10  allowed else exception  */ } }

int Main() {
  Base base = new Base();
  base.method(101-1000); // exception

  Base base2 = new Weak();
  base2.method(101-1000); // ok

  Base base3 = new Strong();
  base3.method(101-1000); // exception 
}

LSP is clearly violated: Weak class for 101-1000 ok, but Base class for 101-1000 throws exception. This is clearly not the same behaviour.

LSP expects, the set of numbers for the base class can be widened (strengthened) in subclasses, but in the main program the set will not be widened and therefore the subclass with weaker preconditions can fullfill the preconditions of the base class.

Same goes for the postconditions the other way.

Alary answered 8/10, 2014 at 23:55 Comment(1)
It doesn't make sense to compare behavior of base-class vs sub-class if you are violating the contract (1-100) of the base-class in the first place. LSP only applies within specification of the base-class. Out-of-specification is unknown behavior.Miyokomizar

© 2022 - 2024 — McMap. All rights reserved.