There are several basic rules for designing a domain object. These rules keep safety and correctness in mind:
- Always implement
equals
andhashCode
- I have seen many memory leaks from inadvertent caches of domain objects which used object identity. When implementing
equals
andhashCode
, only compare primary keys since that is the sense of equality the persistence layer uses. - Mark immutable fields
final
and provide no setters for them - Always mark primary keys
final
and only set them in a constructor. This means there is no default constructor. - Include all required fields in constructors
- Never leave domain objects in an inconsistent state. The constructors should reflect this fact. Again that means no default constuctor.
- Test the inputs of non-
null
fields - Simply provide
if (null == someField) throw new NullPointerException();
in the constructor or setter which takes someField. If the field is not required, make the same check in the getter as it may have not been initialized yet. - Implement
Comparable
if there is a default sort order - Whenever domain objects have any kind of natural sort order, always implmement
Comparable
. Do not force other code to do the work on behalf of the domain object. And remember to compare as many fields as needed by the sort order. For example, aCustomerName
comparator needs to sort on lastName, firstName, middleName and nameSuffix. - Implement
toString
for debugging
- Do not use
toString
for business code or display. For those, use business-specific methods such asdisplayAs
. Commons-lang provides an excellentToStringBuilder
for debugging.
Unfortunately, not all of them can be used with every project. For example, if you persist domain objects directly rather than using an intermediate persistence layer to represent database tables (e.g., Hibernate or iBatis), the framework requries that every instance field have a bean-like getter/setter. (Actually, recent Hibernate supports direct field access, but this is still uncommon with many projects.)
4 comments:
I think that implementing #equals and #hashCode to depend on the primary key(s) is a very bad idea. Imagine the scenario where you have your domain object, modify it and then you compare it against a freshly loaded instance from the DB. Will they be #equal() -- YES. Should they be considered to be equal? Definitely NO. Been there, done that, got the T-Shirt.
Re: "there is no default constructor" -- have you ever actually sent your objects over a physical wire? I.e. by making them serializable? If you ever plan to do this you WILL need a default constructor.
Just my $.02.
Christian
The idea of not leaving your objects in an inconsistent state sounds sweet, but really is idealistic nonsense. This makes it ugly for a persistence layer to load the domain object and basically says "I will never want to consider lazy loading". Also, you end up with uberconstructors. Some day you'll change a field to no longer required and then what? Remove it from the constructor? Great, you have to update all your code... not just what accesses the domain objects, but your test cases, javadocs, etc.
On top of that, forget about ever adding factories, pooling, serialization, or distributed management.
Cris
http://crisdaniluk.com/
I've been down both roads and prefer that persistence objects be fully constructed. I've not had trouble with serialization conserns -- look at non-bean-based implementations such as Walnes' XStream. There is also an interesting question about separating the persistence objects from the domain objects, and making the domain objects use composition only. This changes things a bit.
Post a Comment