Value Objects are the next in our series of Design Patterns. This pattern takes simple values and gives them super powers with the added bonus of catching bugs sooner.
Unexpected values is a common problem in development. You think, “Ok, this will always be a positive number,” and then somehow it’s negative. You think, “This post status will be one of these values,” but then it’s something completely unexpected. Your code breaks in really weird ways and it’s hard to figure out what happened. When you find the issue and realize it was supposed to be impossible, a tiny part of you dies. We need a way of catching impossible values before they run amok and become hard to detect.
If you’ve ever experienced this, then let me introduce you to your new Design Pattern friend: Value Objects. Among other things, Value Objects catch invalid values precisely when they happen!
The Meaning Behind Values
There are three basic types of values to all development: strings (text), numbers, and booleans (true/false). They’re littered through every project and tie everything together. But every basic value has meaning. One doesn’t make numbers arbitrarily, but each number represents something. With that meaning comes constraints. Here are some examples:
- Percentage: a positive number between 0 and 100 (or 0 and 1)
- Temperature: a number with an associated unit (celsius, fahrenheit, or Kelvin)
- Post Status: a limited number of string values (e.g. published, trashed, pending)
At its core, the Value Object Design Pattern seeks to keep the meaning of a value tied to the value itself. Value objects are a type of Structural Pattern, which means they tell us how to structure an object to solve this problem. The best way to understand is to see it in action. Let’s work through a couple examples!
Keeping the Post Status Honest
One of the most common scenarios in development is a string that can only come from a set of values. Let’s say we have a post object. That post’s status reflects whether it can only be viewed by some, is still being written, is finished and publicly visible, or has been trashed. This probably takes the form of: “private”, “draft”, “published”, or “trash”.
It’s important that this status is only one of the four options. If it’s something else then the application may default in some weird way or create an error that’s hard to track. So wherever we talk about this status, we want it enforced in this way. Perfect! Let’s make a PostStatus Value Object!
Alright, so how would we use this?
// Here’s an array with all the possible statuses! $allStatuses = PostStatus::all(); // Here we can make a new instance of the value $privateStatus = new PostStatus(‘private’); $draftStatus = new PostStatus(PostStatus::DRAFT); // Now we have a way to compare the value with others $privateStatus->is(PostStatus::Private); // true $privateStatus->is($draftStatus) // false // Is using __string casting, so we can do string comparisons ( $draftStatus == ‘draft’ ) // true ( ‘private’ === (string) $privateStatus ) //true // Throws an exception $invalidStatus = new PostStatus(‘break the things!’);
Interesting! So now the status is self-aware in a sense. You can compare it with other values or statuses. You can do even more here, but we wanted to keep this short and simple. Most importantly, if anyone tries to create a PostStatus with a value other than the ones allowed, it will throw an exception.
Wait, why is it a good thing that it throws an exception? Ok, here’s the thing (and brace yourself for this):
No one writes perfect code.
Argh! It’s true, I’m afraid. Often times developers work against themselves and keep things moving along when things should really stop working. The reason is that if you control when things break then you leave yourself the best chance of seeing what went wrong and fixing it.
The hardest bugs to fix are those where the issue happened a long-ways back and are just now surfacing.
A value with a limited set of options is called an enumeration (enum for short). When building our projects, every enumeration is a Value Object. That way if any enumerated value gets something unexpected, we know immediately and can figure out why.
Enumerations are a great use case for Value Objects, but they’re not the only one. Let’s look at another, rather different kind.
Working with unit-values such as Temperatures
When doing most kinds of math there is a unit associated with the number. You have X number of what? Miles? Dogs? Degrees? It’s important to know what you have, and this becomes especially useful when conversions are involved. Let’s take a look at what a Temperature Value Object might look like.
And here’s how we would use this:
// Create our temperatures $lowTemp = new Temperature(14, 'fahrenheit'); $highTemp = new Temperature(38.5, 'celsius'); // Conversions! $lowTemp->celsiusValue(); // -10 $highTemp->kelvinValue(); // 311.65 // Compare different temperatures without worrying about units $sameHighTemp = new Temperature(101.3, 'fahrenheit'); $highTemp->equals($sameHighTemp); // true $lowTemp->equals($highTemp); // false // Stop bad values before they happen $invalidValue = new Temperature('thirty-two', 'celsius'); $inavlidUnit = new Temperature(32, 'mystery-unit');
Pretty cool, right? A very common mistake when working with units is that we compare two values, forgetting what unit that value is measured in. But using it doesn’t matter anymore which unit the temperature is because the Temperature instance keeps track of its own unit. So when you compare, the Value Object makes sure it compares in the same unit.
Key Takeaways for Value Objects
Keeping these examples in mind, let’s pull out some principles for working with Value Objects.
- Each Value Object instance should represent a single (scalar) value. It’s not a group of values. It’s just one per instance.
- Keep the value property private. If the value can be changed directly, you lose all that cool checking you have. You can create set methods if you want to change the value, but we prefer to work immutably. That is, you can’t change a value but you can make a new one.
- Store everything you need to make your value meaningful. If you’re dealing with units, store the unit. But only store what it meaningful to only that one value.
- Protect the value’s integrity. Especially in the constructor, throw exceptions if the value isn’t exactly what was expected.
Value Objects are one of the most useful and simple Design Patterns.. They’re easy to understand and can be incredibly valuable (pun intended). At first it may feel like unnecessary complexity, but with a little time they always show their value.
We use Value Objects regularly in our projects and invite you to do the same!