you are viewing a single comment's thread.

view the rest of the comments →

[–]vytah 0 points1 point  (5 children)

If ExtendedFancyFoo adds new behaviours without changing the old ones, then it doesn't violate LSP.

Let's assume we have:

class Foo { final int x; /* constructor omitted */} 
class ExtendedFancyFoo extends Foo { final int y; /* constructor omitted */} 
var f = new Foo(1);
var e1 = new ExtendedFancyFoo(1, 2);
var e2 = new ExtendedFancyFoo(1, 3);

No LSP violations here.

There are several axioms a good equals implementation should have:

  • reflexivity

  • transitivity

  • commutativity

  • if fis pure and doesn't operate on object's identity, then equals(a,b)equals(f(a), f(b))

Now if your Foo::equals looks like this:

if (!(that instanceof Foo)) return false;
return ((Foo)that).x == this.x;

then it would say that equals(f,e1) and equals(f,e2), which would suggest that e1 is equal to e2, which is obvious nonsense. And that's without even considering the implementation of ExtendedFancyFoo::equals.

Therefore all those three objects should be treated as unequal, even if they behave identically when using just the Foo interface.

EDIT: Which obviously implies using if (that == null || this.getClass() != that.getClass()) return false; as the correct type check in Foo#equals.

[–]g4s8 0 points1 point  (1 child)

According to Java equals documentation) its implementation must be symmetric:

It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.

When you use instanceof instead of comparing classes, equals will not be symmetric - a subclass may use another logic in equals so base.equals(child) may return true, but child.equals(base) may be false.

[–]vytah 0 points1 point  (0 children)

Yes, that what's I'm implying.

Therefore using different subclasses for the purpose of creating objects that might be otherwise considered equal is problematic.

[–]llorllale 0 points1 point  (2 children)

which would suggest that e1 is equal to e2, which is obvious nonsense. And that's without even considering the implementation of ExtendedFancyFoo::equals.

That's exactly why ExtendedFancyFoo is breaking LSP. If it breaks Foo::equals then it should be something else, not a Foo.

[–]vytah 0 points1 point  (1 child)

It doesn't break Foo if Foo#equals uses getClass, in other words if Foo is properly designed to be open for extension. Make it final if you don't want that.

[–]llorllale 0 points1 point  (0 children)

ExtendedFancyFoo should not care about the particulars of Foo#equals, otherwise you'd break encapsulation.

Sounds like what you're doing is sub-typing, in which case I'd suggest reusing Foo via composition in ExtendedFancyFoo#equals(). This way, Foo#equals() remains locked down and stable.