So Long, Value Object. Hello, Reference
Maybe after my last post you refactored your code and introduced a bunch of Value objects. And now, perhaps, the bloom is off the rose and you are realizing that Value objects were not the right solution. Value objects can be great, in the right situation. But in the wrong situation, of which there are many, Value objects are trouble. As immutable representations of simple data, Value objects can be powerful tools, but they aren’t the block you build an application with.
Fowler, intentionally I’m sure, set a bit of a trap in “Replace Data Value With Object” when he created a Value object out of the application’s Customer class. I can’t imagine a less likely Value object than a Customer. What business thinks so little of their Customers that they represent them with such an intentionally limited class? It’s only a matter of time until another refactoring is necessary: Change Value to Reference.
What is a Reference? Well, it’s a Reference Object. Wait…what is a Reference Object? Maybe this is a term familiar to you, but it was foreign to me. Fowler describes it as “things like customer or account…you use the object identity test to determine if they are equal.” In our previous code, the Value version of Customer looked like this:
Equality was determined by comparing two instances’ names. A Reference version of customer might look like:
Instead of overriding the equality comparison, we use Ruby’s default ==
method, which compares the instances’ object_id
s. And we find that no two ReferenceCustomers we instantiate are ever equal.
Also, unlike Value objects, Reference objects are mutable. So we can add a method that allows us to change a Customer’s name.
Being able to update our Customers seems great, but being unable to compare Customers seems of dubious value. However, this is definitely the road Fowler wants us to follow. Let’s roll with it and see where it goes.
Now that we know what a Reference object is we can follow the steps of the Replace Value with Reference pattern. First is “Replace constructor with Factory Method”.
The constructor we’re replacing is in Order:
“Replace Constructor with Factory Method” is a pattern in Refactoring (page 304, if you’ve got a copy at hand). I’m going to skip the steps of that pattern and go straight to the end:
Two big changes to dig into. First, Order is now using the Customer.create
factory method. Second, in Customer we’ve forced all instantiation to go through create
. The private_class_method :new
line prevents the use of Customer.new
(well, mostly. This is still Ruby after all). The logic behind this change is simple enough: if you’re going to put object creation logic in a custom method, then make sure that everyone uses that method.
Privatizing the initializer isn’t something I’ve seen very often in Ruby, but it lines up nicely with Declarative Builders, which I wrote about a year ago. And the technique does crop up in real projects as well, so it’s useful to know.
But, we still haven’t solved the equality weirdness from earlier:
Fowler tackles this in steps two and three:
- Decide what object is responsible for providing access
- Decide whether the objects are pre-created or created on the fly
He uses Customer to provide access to a collection of pre-created Customer instances.
And that solves the equality problem, though the name of the create
method is now a little odd since we’re not creating anything. A simple renaming fixes the problem:
Is this a solution that you expect to see in your code? I don’t know about you, but I don’t expect my Customer class to pre-load a bunch of Customer instances so that it can store them in an Class-level instance variable.
But, if you swap out “Class-level instance variable” for “Relational database”, then this code looks pretty familiar:
In Fowler’s example, Customer knows how to retrieve instances of itself from the store and – with a couple more methods – it could also know how to put new instances into the store. If you retrieve the same customer twice, your two instances will be equal. The ActiveRecord version has the exact same knowledge and abilities*, though the implementation of those abilities is in code that you probably don’t look at that often. I know I don’t.
The two implementations offer the same features, but they suffer from the same drawback as well – mutation. In ActiveRecord you could be dealing with a database record that has changed out from underneath you. In Fowler’s internal-store approach, another user of Customer could change the object you’re using. Like I said earlier, Reference objects, unlike Value objects, are mutable. Fowler is pretty clear about this:
you want to give it some changeable data and ensure that the changes ripple to everyone referring to the object.
If your goal is for that ‘ripple’ to be instant then Fowler’s implementation is clearly superior to the ActiveRecord approach.
But with the internal store of object references:
Which just reflects the value of using references to objects in memory instead of data in a database. Obviously this approach won’t work in all situations. You probably can’t store your entire application database in memory. But in the right situations, it’s very helpful.
In the end, where and how you choose to store the reference objects is an implementation detail. It’s not really the point of this refactoring. Instead, the point is how you can give your classes even more powerful collaborators. In the beginning, Customer was just a string, then it became a useful-but-limited Value object; now, as a Reference object, it’s far more powerful and (possibly) error prone. Which solution is the right one depends on your needs.
You may also need to sign up for my free newsletter, in which I usually talk about code smells and assorted nonsense. Check out previous issues if you want to catch up on my previous ramblings. Comments/feedback/&c. welcome on twitter, at ian@ianwhitney.com, or leave comments on the pull request for this post.
* Well, not exactly. In our internal-store implementation the object_id of x
and y
will be the same. In the ActiveRecord version they will be different. ActiveRecord defines equality by comparing the id
values of the two objects. back