Value Object has benefits outside of Domain Driven Design

Written by Simon Guindon. Posted in Coding

I was looking at a change set on a source repository to learn how something was implemented and realized a bulk of the change set was changing an identifier field from an int to a string. I’m not going to go into the details why but this is a simple refactoring exercise. The alarming thing was how much code was affected by changing only one thing. We’ve all done refactoring like this where a data type doesn’t suite our needs in a later iteration right?

What came to mind for me in this instance was Domain Driven Design’s Value Object (don’t confuse it with the CLR’s Value Type). A good description for the people who haven’t read about DDD yet is: "An object that represents a descriptive aspect of the domain with no conceptual identity is called a VALUE OBJECT." [Evans 2003]

A Value Object isn’t something that would represent for example a user in a system. A user is uniquely identified and should be represented as an Entity. A Value Object could represent something like an Address or Price.

Value Objects encapsulate the data type and isolate much of the data type specifics within the Value Object class. This means if you refactor that data type the collateral damage is dramatically reduced. In the case of Price, instead of the code spread throughout your code base being coupled to a data type like double you would create a class named Price which encapsulates the double. This gives you the freedom to change the internally held data type with minimal change.

In the case that a price is defined as a double, how do we want to represent when something has no price versus when it is simply just free? Some code may treat it with a –1 value meaning it has no price (not suggesting this). What if you decide –1 doesn’t work for you and you change it? What if you change it to a string, do you check for “-1” now? These are the kinds of changes I saw in the refactoring. The meaning of “zero” completely changed and impacted the code base throughout two dozen classes.

If Price was a Value Object in the sense defined by DDD then we would wrap all this in a class that may look something like this:

public class Price
{
    const double ZERO = 0.0d;
    double value = ZERO;

    public Price(double value)
    {
        this.value = value;
    }

    public double Value
    {
        get { return value; }
    }

    public override bool Equals(object obj)
    {
        if (obj == null) 
            return false;

        if (this.GetType() != obj.GetType())
            return false;

        Price comparisonPrice = obj as Price;

        return this.Value == comparisonPrice.Value;
    }

    public override int GetHashCode()
    {
        return value.GetHashCode();
    }

    public static Boolean operator ==(Price v1, Price v2)
    {
        if ((object)v1 == null)
        {
            if ((object)v2 == null)
                return true;
            else
                return false;
        }

        return (v1.Equals(v2));
    }

    public static Boolean operator !=(Price v1, Price v2)
    {

        return !(v1 == v2);
    }

    public static Price Zero
    {
        get { return new Price(0.0d); }
    }
}

This gives us the ability to do things such as:

Price price1 = Price.Zero;
if (price1 == Price.Zero)
    Console.WriteLine("Price1 is ZERO!");

Price price2 = new Price(1.0);
Price price3 = new Price(1.0);
if (price2 == price3)
    Console.WriteLine("Price2 and Price3 are EQUAL!");

Price price4 = new Price(1.0);
Price price5 = new Price(1.1);
if (price4 != price5)
    Console.WriteLine("Price4 and Price5 are DIFFERENT!");

The great thing about using Value Object this way is all the comparison code throughout your code base doesn’t need to change based on your changes in the future of what a Price data type may be or what zero may mean in the future if you choose to change that.

Another benefit of Value Object is the ability to be immutable. If you want to reduce bugs and prevent developers or bad code from accidentally modifying a value you can simply only allow the value to be set in the constructor and not in any setter as I did above. This reduces chances of bad behaviour altering values.

Using alternative constructors you can also provide migration paths (although I’m not sure if this is a well advised move) to take different data types and convert it to the new data type after a refactoring of the Price’s internal data type.

Value Objects provide more flexibility, isolates refactoring impact and reduces bugs. It’s a little bit more work but not much! It leaves the code base in better hands for the next developer who may need to refactor the choices I made.

Next time I catch myself exposing a data type directly and require calling code to couple to the data type I hope to remember where I can leverage Value Objects.

Tags: , , ,

Trackback from your site.

Simon Guindon

Distributed Systems Engineer

Leave a comment

Startups