Examples of value objects are numbers, dates, strings, email addresses, isbn numbers, etc.. Value objects are usually small and simple, their identity is based on their state, not on the object identity. Since they are usually immutable they can be put into good use in optimization in conjunction with the flyweight pattern.
Let's take a look at the following example to see how value objects can be used to improve design.
class Contact {
private String name;
private String emailAddress;
public Contact(String name, String emailAddress) {
this.name = name;
this.emailAddress = emailAddress;
}
}
When creating an instance of the Contact class or Recipient class, we need to make sure the email address is valid.
class EmailValidator {
public static boolean validate(String emailAddress) {
...
}
}
class Test {
public void createContact(String name, String emailAddress) {
if(EmailValidator.validate(emailAddress)) {
Contact contact = new Contact(name, emailAddress);
} else {
throw new Exception("Can't create Contact with an invalid address");
}
}
}
Since there is no reason to allow creating a contact object with an invalid email address, moving the validation to Contact's constructor seems to be a logical step:
class Contact {
private String name;
private String emailAddress;
public Contact(String name, String emailAddress) {
if(EmailValidator.validate(emailAddress)) {
this.name = name;
this.emailAddress = emailAddress;
} else {
throw new Exception("Contact with an invalid address");
}
}
}
class Test {
public void createContact(String name, String emailAddress) {
Contact contact = new Contact(name, emailAddress);
}
}
This solution does the job but it is not really elegant. The Contact object is responsible for validating the state of one of it's members. That should be the member's responsibility (principle of separation of concerns). It may not seem like a big deal but in fact it is. Imagine doing this in the context of a larger system: there is a copy-paste scenario already
class Contact {
private String name;
private String emailAddress;
public Contact(String name, String emailAddress) {
if(EmailValidator.validate(emailAddress)) {
this.name = name;
this.emailAddress = emailAddress;
} else {
throw new Exception("Contact with an invalid address");
}
}
}
class MailingList {
private String[] emailAddress;
...
public addAddress(String emailAddress) {
if(EmailValidator.validate(emailAddress)) {
...
}
}
}
To address this, it is often a good practice to define a value object to represent an email address. The value object will be responsible for its own validation:
class EmailAddress {
private String emailAddress;
public EmailAddress(String emailAddress) {
if(Validator.validateEmail(emailAddress)) {
this.emailAddress = emailAddress;
} else {
throw new Exception("Invalid email address");
}
}
}
class Contact {
private String name;
private EmailAddress emailAddress;
public Contact(String name, EmailAddress emailAddress) {
this.name = name;
this.emailAddress = emailAddress;
}
}
class MailingList {
private EmailAddress[] emailAddresses;
...
public addAddress(EmailAddress emailAddress) {
...
}
}
As a result the code got more readable, and the validity of email addresses is conveniently ensured throughout the system.
There is an inherent danger if a value object is mutable and passed by reference to other objects: if it's state gets modified, can lead to unwanted results:
class EmailAddressMutable {
...
public void setEmailAddress(String emailAddress) {
if(Validator.validateEmail(emailAddress)) {
this.emailAddress = emailAddress;
} else {
throw new Exception("Invalid email address");
}
}
}
class TestMutableRisks {
public void testMutable() {
EmailAddressMutable emailAddress1 = new EmailAddressMutable();
emailAddress1.setEmailAddress("noone@example.com");
EmailAddressMutable emailAddress2 = emailAddress1;
emailAddress.setEmailAddress("someone@example.com");
/*
emailAddress1 will also contain the string: "someone@example.com" ..
not the behaviour we usually want to see from a value object
*/
}
}
Because of this it's best to keep value objects immutable or at least copied by value, not by reference.