Let me explain with a simple example.
Say you have a Customer entity, that in your domain model is an interface, that is, a type:
public interface Customer {
public String getCustomerCode();
public String getSurname();
public Date getBirthdate();
}
You have different concrete Customer implementations, i.e. StandardCustomer and PremiumCustomer, but customer equality is the same for all customers, because defined by the customer code: so, you want to define equality for the Customer type.
Too bad, in Java the only way to go is to implement a base class for all customers, defining, among other methods, the equals() one:
public abstract class BaseCustomer implements Customer {
public String getCustomerCode() { ... }
public String getSurname() { ... }
public Date getBirthdate() { ... }
public boolean equals(Object obj) {
if ((obj == null) || (!(obj instanceof Customer))) return false;
Customer other = (Customer) obj;
return this.getCustomerCode().equals(other.getCustomerCode());
}
}
public class StandardCustomer extends BaseCustomer {
...
}
public class PremiumCustomer extends BaseCustomer {
...
}
However, this is not correct, nor safe, because you could implement another Customer directly implementing the interface, or even extending another base class: doing so, you would break the equals contract.
For example:
public class CustomerOfTheMonth implements Customer {
public String getCustomerCode() { ... }
public String getSurname() { ... }
public Date getBirthdate() { ... }
// You forget to implement equals(), or maybe implement a different one ....
}
Now, a StandardCustomer is equal to a CustomerOfTheMonth, but a CustomerOfTheMonth is not equal to a StandardCustomer!
This is because BaseCustomer defines equality for all Customers, but CustomerOfTheMonth, while being a Customer, is not a base one!
So, the symmetry of the equals() method is broken!
The problem would be solved if we could implement the equals() method into the Customer interface, rather than into the BaseCustomer concrete class: if you had then to implement equality in a different way for some concrete class, you'd be also free to overwrite it later!
Here we come back to the beginning of my post: why doesn't Java provide interface level equality?
16 comments:
Well, for one it would not be enough to implement equals() given the hashCode() contract. Then again, I'm a bit puzzled at why two instances of different classes should actually be equal. Two suggestions to overcome this "limitation":
1. add an equivalentTo() method in your interface if you need to fullfil a specific business need
2. use instanceof BaseCustomer in your abstract class.
All in all, though, I'm not quite sure business-level logic should rely on equals() to do business-level work. Care to provide a use case?
Not only that, but it's possible for a class to extend BaseCustomer and still break the contract by overriding the equals method. You should really make it final.
However, I second Gianugo's question: why should two instances of different classes be considered equal?
Hi Gianugo,
thanks for your answer.
> 1. add an equivalentTo() method in your
> interface if you need to fullfil a
> specific business need
Yes, interesting point. However, I think the client user could get confused: why the API provides an equivalentTo() method if there's also the equals() one?
> 2. use instanceof BaseCustomer in your
> abstract class.
This would not break the equals() contract, but it doesn't provide type equality for all Customers!
> All in all, though, I'm not quite sure
> business-level logic should rely on
> equals() to do business-level work. Care
> to provide a use case?
Equality concept, hence the equals() method in Java, is fundamental for expressing and determining entities in your (rich) domain.
Say you have a StandardCustomer: John Miller. John, after a single order of one thousand dollars, becomes cutomer of the month. So, John will always be a StandardCustomer, but until the end of the month, it will be also a CustomerOfTheMonth, with additional behaviour, business rules or alike. So, John will be represented by two different "subtypes", say depending on the context, but into your system you need to know it is always the same Customer!
Hope to have clarified a bit.
Any thoughts?
Hi Ugo,
thanks for answering!
> it's possible for a class to extend
> BaseCustomer and still break the
> contract
> by overriding the equals method. You
> should really make it final.
Yes, I fully agree.
> However, I second Gianugo's question:
> why should two instances of different
> classes be considered equal?
When you deal with rich domain models, I think this often happens.
Take a look at my comment to Gianugo for an example.
What do you think?
Say you have a StandardCustomer: John Miller. John, after a single order of one thousand dollars, becomes cutomer of the month. So, John will always be a StandardCustomer, but until the end of the month, it will be also a CustomerOfTheMonth, with additional behaviour, business rules or alike. So, John will be represented by two different "subtypes", say depending on the context, but into your system you need to know it is always the same Customer!
Sorry, but how can the same John Miller be represented by two different objects and even more, two different types? If the type of an entity can change in time, then you can't use subclassing to express that changing concept, you need to introduce State/Strategy there.
John Miller is a Person or Customer or whatever, then you have the State which is the CustomerType (which has the subclasses CustomerOfTheMonthType and others).
So, in time, you can change the State of a Customer and your equals() and hashCode() on Customer (maybe better to implement them on the natural keys of the entity and not an id) will never be broken.
Isn't it so or I misunderstood something?
Hello Sergio,
maybe what you want is to handle the equality between classes as an aspect.
In this perspective, you can easily provide a default implementation for all classes implementing your Customer interface (this technique, as you may already, produces code sometimes referred as mixin).
A possible implementation, using AspectJ, would be:
public interface Customer {
...
public boolean equals(Object obj) {}
static abstract aspect IMPL {
public boolean equals(Object obj) {
if ((obj == null) || (!(obj instanceof Customer))) return false;
Customer other = (Customer) obj;
return this.getCustomerCode().equals(other.getCustomerCode());
}
}
}//definition of Customer itf
Hi Alessio,
thanks for answering.
> Sorry, but how can the same John Miller
> be
> represented by two different objects and
> even more, two different types? If the
> type of an entity can change in time,
> then
> you can't use subclassing to express
> that
> changing concept, you need to introduce
> State/Strategy there.
State and Strategy patterns are useful for expressing changing behaviours, not for changing the interface, adding so behaviours.
A pattern which modifies and also adds behaviour is the Decorator, which actually changes the object interface. And I have to say I see no problem in having an object, of type Customer, that is two different subtypes depending on some particular context. I'd rather say this could be a common case in rich domain models, because they represent reality, and reality always changes ;)
> John Miller is a Person or Customer or
> whatever, then you have the State which
> is the CustomerType (which has the
> subclasses CustomerOfTheMonthType and
> others).
> [CUT]
> Isn't it so or I misunderstood
> something?
As already said, this simply would not solve the problem of adding behaviour to the Customer interface.
Any other thought?
Hi Valerio,
thanks for your interesting point: using AOP is surely a viable solution.
However, would it not be a lot easier if Java already had this capability?
Regards,
Sergio B.
Hi Sergio,
I get the point. The problem is that I misunderstood your previous comment (the part I quoted), because I thought you were speaking about having 2 instances of John under two different types, and that *really* sounded weird! :-)
Anyway, interesting discussion, it seems like the part-2 of your other entry about solving interface contracts :-)
I still contend that relying on equals() to express business-level equality is wrong. If you consider equals() as the Java counterpart of pointer equivalence in C, this becomes apparent. You should also consider how equals() requires (but doesn't force) to override hashCode() to understand why you'd be better served by providing an explicit equality contract at the business level. Leave equals() alone to JVM-specific stuff, and move business concerns to the business layer.
Alessio Pace said...
> I get the point. The problem is that I
> misunderstood your previous comment (the
> part I quoted), because I thought you
> were
> speaking about having 2 instances of
> John
> under two different types
No, I was not talking about this, even if I think this is not a problem, nor a weird thing.
If you carefully think, we usually make a lot of mess with the concepts of type, subtype and entity, when we should instead just think in terms of entities.
What is John in our domain?
A Customer entity.
Then, if it is a StandardCustomer, a CustomerOfTheMonth, or both, it doesn't matter too much: it IS A Customer entity, always.
It would actually be a real weird thing if John were a Customer AND also a Product (beyond the non-sense), because they are two different types of entities!
Hope to have explained well.
> Anyway, interesting discussion
Yes, thank you.
I hope to make other interesting discussions with you ;)
Cheers,
Sergio B.
Gianugo Rabellino said...
> I still contend that relying on equals()
> to express business-level equality is
> wrong.
Yes, I agree: if equality is a strong business concept, implementing it as a separate method could be a better thing.
However, this was not my point of discussion, and I still think that if Java provided interface level equality, it would be a lot clearer to develop pure domain models.
Thanks for your interesting points, Gianugo.
Cheers,
Sergio B.
Hi Sergio,
thanks for the answer and let me quote and comment again, I think I see where we got into confusion :-D
What is John in our domain?
A Customer entity.
Then, if it is a StandardCustomer, a CustomerOfTheMonth, or both, it doesn't matter too much: it IS A Customer entity, always.
Yes, what I meant is indeed that it is ONE Customer entity, being a StandardCustomer or CustomerOfTheMonth or both, depending on the needed/offered abstraction. But in the end is just ONE entity (there is only one John Miller).
Are we on the same line now? Hope so :-)
Alessio Pace said ...
> But in the end is just ONE entity (there
> is only one John Miller).
> Are we on the same line now? Hope so :-)
Yes, we are :)
Thanks,
Cheers,
Sergio B.
hello Sergio,
>using AOP is surely a viable solution.
>However, would it not be a lot easier if >Java already had this capability?
you can obtain the same effect in pure java relying on the dynamic proxies mechanism (much like as the original spring-aop works when implementing mixin, as you probably know better than me).
ciao,
valerio
Valerio Schiavoni said...
> you can obtain the same effect in pure
> java relying on the dynamic proxies
> mechanism
Call it Dynamic Proxies, call it AOP ... it is always extra code to write ;)
I meant it would be better to have this feature fully integrated in the Java language.
Regards,
Sergio B.
Post a Comment