An object defined primarily by its identity is called an ENTITY.This implies that the primary responsibility of an entity is maintaining this thread of continuity, this identity.
Implementation wise, this is typically accomplished by using an identity field, a 'primary key' field as it were. Here is an example using JPA annotations:
@Entity @Table(name = "PERS") public class Person { @Id @Column(name = "ID") @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PERS_ID_SEQ") @SequenceGenerator(name = "PERS_ID_SEQ", sequenceName = "PERS_ID_SEQ", allocationSize = 20) private Long id; @Column(name = "NAME") private String name; @Column(name = "DOB") private Date dateOfBirth; public Long getId() { return this.id; } @SuppressWarnings("unused") private void setId(Long id) { this.id = id; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public Date getDateOfBirth() { return this.dateOfBirth; } public void setDateOfBirth(Date dateOfBirth) { this.dateOfBirth = dateOfBirth; } public String toString() { return this.name; } }Pretty straightforward code. Still, there's a lot of it and it will baloon quickly when you add more fields, constructors and serializability to the mix.
Say that we need to determine in some part of the application whether or not a person is a minor. Naively, we could put an isMinor() method on the Person class. However, the fact of the matter is that the core responsibility of the Person class is maintaining identity, and determining whether or not a person is a minor is not part of that responsibility. Futhermore, the size of the Person class indicates that it's already plenty busy with it's core responsibility.
Eric Evans also hints at this in his book:
Rather than focusing on the attributes or even the behaviour, strip the ENTITY object's definition down to the most intrinsic characteristics, particularly those that identify it or are commonly used to find or match it.It's better to factor the new behaviour into a separate class. One way of doing this that I particularly like is using interpretation wrappers. Here's an example:
public class InterpretedPerson() { private Person person; public InterpretedPerson(Person person) { this.person = person; } public Person getPerson() { return this.person; } public int getAge() { // calculate the age based on person.getDateOfBirth() // ... } public boolean isMinor() { return getAge() < 18; } }This class simply interprets the data available in a Person entity, for instance using the dateOfBirth property to determine whether or not the person is a minor. Seperating data interpretation from the underlying entities brings several advantages:
- The entity classes remain focussed on identity and the associated attributes.
- You can have several interpretation wrappers, doing different kinds of interpretation (for instance, another part of the application might use another definition of what it means to be a minor).
- Responsibilities are properly factored.
Hi Erwin,
ReplyDeleteHow would you use this pattern (where is InterpretedPerson instantiated) and how is it better than simply having a static method isMinor(Person)?
cheers, Guus
Guus,
ReplyDeleteIn the example, the InterpretedPerson is a lightweight wrapper object, so you can just instantiate it when you need it, or use a static factory method to do so. For instance:
if (interpret(person).isMinor()) {
...
}
Where 'interpret(person)' is a static factory method that just returns 'new InterpretedPerson(person)'.
Having a static isMinor(Person) method has some downsides compared to using a wrapper. For one, the interpretation wrapper could hold state to make it's job easier. Also, InterpretedPerson is its own type so you could pass it to methods or subclass it.
Erwin
Guus,
ReplyDeleteanother advantage of the wrapper is that sometimes you need more context than just the entity that you're interpreting.
For example, the project I'm currently working on has a bitemporal data model, so we always need the additional context of the required validity and know times. Using a wrapper like that allows us to pass that only once (and pass the wrapper around as a whole).
Using static methods would be much uglier.