Hi Alex,
> It's really a shame when people cite LSP as if it were
> some kind of golden rule. In fact, it doesn't exist, and
> never has - it was cited as a 'wouldn't it be nice if'
> rule and is trivially disproven:
>
>
http://alblue.blogspot.com/2004/07/java-liskov-substution-p> rinciple-does.html
>
> The problem is that you can't, in fact, blindly substitute
> one thing for another hand have your program work in
> exactly the same way. Mocking is an explicit example of
> this, where you don't want your program to work the same
> way (but want to supply a different implementation that
> does nothing/is testable/doesn't have the setup costs
> etc.
>
On LSP, I brought it up in the book chapter and article because I'd heard it as an objection to the
canEqual
approach. So I wanted to put an "LSP defense" in the text to head this objection off at the pass. When I did, I looked up the original text and it also looked to me like Liskov was saying that subtypes must have exactly the same behavior as supertypes. But what the world thinks LSP means is that *at some level of abstraction" the behavior is compatible, or that the subtype behavior must fulfill the semantic contract of its supertype. I felt a bit uneasy saying the latter is what LSP means, because I also wasn't sure that's what Liskov meant, but it is what the general usage of the term LSP seems to mean.
> Unfortunately, far more people use Bloch's implementation
> (using instanceof) instead of one which preserves
> transitivity and obeying the contract as is.
>
> You can either (a) test for instance field equality (using
> getClass and friends) or (b) test behavioural equality
> using the results of property accessors, but mixing the
> two is self-inconsistent.
>
I'm not sure I understand your b. Or maybe your whole last paragraph. Can you clarify?
What you wrote in 2004 in the blog post you linked to is what I think has been the conventional wisdom in the Java community for a long time, although it wasn't necessarily widespread wisdom. You wrote:
Specifically, in order to rely on the LSP, you have to ensure that M> obeys the contract defined in T, which implies that it must be
reflexive,
symmetric and
transitive. If you don't have that, you don't have a valid overridden method as per the LSP definition, so you can't even use that as your argument.
Lastly, if you do try and use
instanceof
, you can guarantee that you break
symmetry, because the
instanceof
is asymmetric. I did point out in my proof on JavaWorld, that it is possible to overcome this behaviour; however, for any implementation of
.equals()
using
instanceof
that obeys
reflexivity and
symmetry will be forced to break transitivity.
So the arguments are set in stone. The LSP doesn't hold in Java, and never has.
--end of Alex quote
It is true that if you use
instanceof
in the first two ways shown in this article, you either break symmetry or transitivity, and that if you use
getClass
you can fulfill the
equals
contract. This is basically what you're saying in the above quote. But what's been missing from the discussion is that there is a technique that lets you use
instanceof
while at the same time maintaining the
equals
contract, and that's what this article is trying to show. The benefit of
instanceof/canEqual
over just using
getClass
is that subclasses instances can equal superclass instances. The writer of the subclass decides whether this is possible based on whether or not he or she overrides the
canEqual
method.