Some time ago I was asked this automated question on a programming website:
Which variable type would you use for representing the age of a person?[ ] Integer[ ] Boolean[ ] String
I smiled and left the website.
The correct answer was none of the three. To represent the age of a person, you should use an Age type.
“Is an integer not enough?“ — you may ask. No.
An integer has different properties and operations that age hasn’t.
The simple truth is, an Age is not an integer and therefore can’t be represented as such.
The bad practice of using primitive types to represent an object in a domain is so common that has even a name: primitive obsession.
But wait, I’m not finished with integers yet.
How do you make sure that an integer variable representing an age is initialized with a valid value? You need to explicitly check before assigning it, in every assignation occurrence in your code.
And how do you make sure that your integer value is not accidentally modified at a later time? You can’t, unless you are using a language that allows for immutable variables and you declare yours as such.
There are here enough points to say that primitives are not good enough for modeling objects in your domain.
You need a different atomic unit of information that satisfies the precision of your domain and its semantical nuances.
Search no more, since this unit already exists. It is called Value Object.
A Value Object represents a typed value in your domain. An Age, for example.
It has three fundamental characteristics: immutability, value equality and self validation.
The bad practice of using primitive types to represent an object in a domain is so common that has even a name: primitive obsession.
A Value Object is immutable.
It means you can’t change its internal value(s) after creating it. No setters allowed.
After injecting one or more parameters into the constructor, there’s no way back. That object will remain the same no matter what until it gets disposed by the garbage collector.
Immutability brings two great advantages:
You can share any Value Object by reference because, being immutable, it won’t be modified in another part of the code.
This dramatically lowers accidental complexity and cognitive load required to avoid introducing any bug.
This is gold especially when your code will run in a multi-threaded environment.
No chances to modify this object after construction
Combine immutability with another rule: do not add any getter by default to your Value Object.
Restrain — I repeat — restrain from adding any method just because “I may need it later”.
Your initial class should have a constructor and a bunch of private properties only.
This allows you to think about the transformation of your data at a later time. It means you can decide the semantics of your methods when you understand the exact use case(s) for your Value Object.
Doing so will improve your model by avoiding pointless interfaces and defining meaningful names and behavior for your Value Object.
Typically, this is how you manipulate a Value Object:
Imposed immutability forces you to express these cases in a language relevant to your domain and perfectly clear to any future reader of your code, yourself included.
Let’s see an example.
Take a closer look to the add method: did you know that in most object oriented languages, you can access the private properties of another object of the same class?
Imagine you and your friend pick one card each from two different poker decks and you want to understand if you have picked the same card. How do you do that?
What you would probably do is to check whether the two cards show the same number and are of the same suit. In other words, you check whether they have the same properties.
Imagine now that you exchange the two cards. You take the one from your friend, your friend yours. Has anything changed? No. You still have the same card and so your friend. Having the same properties, the two cards are indistinguishable one from the other.
Turns out the two cards are in fact Value Objects.
Two Value Objects with same internal values are considered equal. This means you can test for equality by checking if their internal values are all respectively equal.
Note to Java developers: please do not use that equals and hashCode nonsense.
This line of code in Scala has the same functionality as the Java version. Look at how short and elegant it is.
A Value Object only accepts values which make sense in its context. This means you are not allowed to create a Value Object with invalid values.
A Value Object must check for the consistency of its values when they are injected into the constructor. If one of the values is invalid, a meaningful exception must be thrown.
This means no more _if_s around the instantiation of an object. Every formal validation happens at construction time.
This enforced validation is also useful to express domain constraints in a meaningful and explicit way.
Let’s see an example.
In conclusion, do you want to build a Value Object which is robust, expressive and perfectly fits your domain use case?
This is the list you must always check it against:
If you follow this checklist your codebase will never look the same again.
Reading through it will be easier than even. It will feel great.
And all of this simply because of well crafted Value Objects.