问题
The Liskov Substitution Principle states:
Invariants of the supertype must be preserved in a subtype.
I'm particularly interested with the intersection of this principle and polymorphism. In particular subtype-polymorphism though, in fact, this seems to be the case with parametric polymorphism and Haskell type classes.
So, I know that functions are subtypes when their arguments are contravariant and their return types covariant. We can assume that methods are just functions with an implicit "self" argument. However, this seems to imply that if a subclass overrides a method of the parent, it is no longer a subtype, as one of it's methods is no longer a subtype.
For example. Take the following pseudo-code:
class Parent:
count : int
increment : Parent -> ()
{
count += 1
}
class Child inherits Parent:
increment : Child -> ()
{
count += 2
}
So going back to the LSP: can we say that a property of Parent.increment()
should hold for Child.increment()
even though these two don't obey a strict subtyping relation?
More generally my question is: how do the rules of subtyping interface with the more specific arguments of polymorphic functions and what is the correct way of thinking about these two concepts together?
回答1:
Quoting Wikipedia's article on Liskov Substitution Principle
More formally, the Liskov substitution principle (LSP) is a particular definition of a subtyping relation, called (strong) behavioral subtyping [...]
Behavioral subtyping is a stronger notion than typical subtyping of functions defined in type theory, which relies only on the contravariance of argument types and covariance of the return type. Behavioral subtyping is trivially undecidable in general [...]
There are a number of behavioral conditions that the subtype must meet:
- Preconditions cannot be strengthened in a subtype.
- Postconditions cannot be weakened in a subtype.
- Invariants of the supertype must be preserved in a subtype.
Thus LSP is a stronger definition for sub-typing, that relies on features beyond type-theory.
In your example, this rises and falls on your invariant.
calling increment will increase count by **exactly 1**
Obviously Child can't be expressed in terms of Parent since the invariant is broken. This can't be deduced from syntax alone.
LSP should lead you to define Parent and Child separately, having them both inherit from Incrementable
which has a weaker post condition.
回答2:
The term "subtyping" is technically a syntactic matter. So syntatically, Child <: Parent
.
Liskov principle is about behavioral subtyping, as noted in wikipedia. It requires syntactic subtyping, but it also depends on your definition of the class' invariant and pre/post conditions. Since you didn't define any, it is nonsensical to talk about violations.
If you define increment
's postcondition as new count = old count + 1
, there is a violation.
If you define increment
's postcondition as new count > old count
, there isn't any.
In general, defining postconditions to be "exactly the parent's postcondition" makes inclusion-polymorphism impossible by defintion. Where polymorphism makes sense, the definition of postcondition should be relaxed.
Note that class invariant is about possible values - snapshot of the object - and since you can define Child's increment
in terms of Parent's increment
, it cannot violate any invariant.
来源:https://stackoverflow.com/questions/24952395/how-to-think-about-polymorphism-with-subtyping