Sunday, September 16, 2012

Value object obsession

Primitive obsession is one of the more popular (hyped?) code smells these days.
Primitive obsession is the name of a code smell that occurs when we use primitive data types to represent domain ideas. For example, we use a string to describe a message or an integer to represent an amount of money.
The antidote is creating a value object instead of using a primitive. A value object is an immutable object which (in its DDD definition) carries no identity, but is compared based on the value of its attributes.

While primitive obsession is still very much the standard, newer projects seem to regularly suffer from the other extreme; introducing value objects as the de facto standard.

For example, let's look at the implementation of this AccountNumber class I found in the first C# DDD Github repository.

The equality implementations are only included in the last snippet.
public class AccountNumber
{
    public AccountNumber(string value)
    {
        Value = value;
    }

    public string Value { get; private set; }
}
Expressing intent

It's possible that the author of this snippet thought that extracting this object would help express intent, justifying abstraction early on. I don't see it though. To me, it seems to be introduced prematurely, screaming YAGNI. Clients are capable of thinking in primitives; be it text (string), number (int)... Obvious and consistent naming can get you far.

Composition

One of the scenarios which justify the extra boilerplate, is composition.

If we would be interested in the different parts of an account number, we could introduce a composite object, assembling all its parts through the constructor.
public class AccountNumber
{
    public AccountNumber(
        string countryCode, 
        string checkDigits,
        string basicAccountNumber)
    {
        CountryCode = countryCode;
        CheckDigits = checkDigits;
        BasicAccountNumber = basicAccountNumber;
    }

    public string CountryCode { get; private set; }

    public string CheckDigits { get; private set; }

    public string BasicAccountNumber { get; private set; }            
}
The account number is self contained now; its parts won't leak all over the place.

Valid instantiation

Another valid reason to introduce a value object is when you need to guarantee valid instances.

In this example, I could guard for empty values in the constructor, taking that responsibility/problem out of consumers' hands.
public AccountNumber(
    string countryCode, 
    string checkDigits,
    string basicAccountNumber)
{
    Guard.NotEmpty(countryCode);
    Guard.NotEmpty(checkDigits);
    Guard.NotEmpty(basicAccountNumber);

    CountryCode = countryCode;
    CheckDigits = checkDigits;
    BasicAccountNumber = basicAccountNumber;
}
Encapsulating behaviour

One more important reason to use a value object, is when there is behaviour to add; encapsulation makes for good OO. In the example below, the Check method - which verifies the check digits - is contained by the AccountNumber class itself, abstracting the algorithmic logic and preventing duplication.
public class AccountNumber
{
    public AccountNumber(
        string countryCode,
        string checkDigits,
        string basicAccountNumber)
    {
        Guard.NotEmpty(countryCode);
        Guard.NotEmpty(checkDigits);
        Guard.NotEmpty(basicAccountNumber);

        CountryCode = countryCode;
        CheckDigits = checkDigits;
        BasicAccountNumber = basicAccountNumber;
    }

    public void Check()
    {
        // verify checkdigits
    }

    public string CountryCode { get; private set; }

    public string CheckDigits { get; private set; }

    public string BasicAccountNumber { get; private set; }

    public bool Equals(AccountNumber other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return Equals(other.CountryCode, CountryCode) && 
                Equals(other.CheckDigits, CheckDigits) && 
                Equals(other.BasicAccountNumber, BasicAccountNumber);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != typeof (AccountNumber)) return false;
        return Equals((AccountNumber) obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int result = CountryCode.GetHashCode();
            result = (result*397) ^ CheckDigits.GetHashCode();
            result = (result*397) ^ BasicAccountNumber.GetHashCode();
            return result;
        }
    }

    public static bool operator ==(AccountNumber left, AccountNumber right)
    {
        return Equals(left, right);
    }

    public static bool operator !=(AccountNumber left, AccountNumber right)
    {
        return !Equals(left, right);
    }
}
Summarized

Introducing value objects instead of simple types, can add useless boilerplate and unnecessary levels of indirection. Value objects should arise out of certain necessity, and should not be the de facto standard.

Turn to using value objects:
  • when they help express intent
  • for composition
  • to guarantee valid instantiation
  • to encapsulate behaviour

If you disagree, please show me the light.

4 comments:

  1. While I agree that value objects are sometimes overused, I would like to point out that the examples here suffer from one big problem: none of them implement equality, which is a very important aspect of implementing value objects that will behave correctly in all circumstances.

    Easiest way to do it is to just let R# generate both the equality and hashcode implementation based on the encapsulated values that make the 'value object' what it is.

    ReplyDelete
    Replies
    1. Agreed. I mentioned that somewhere on top, but I left the implementations out for the sake of brevity of the code snippets. I should have mentioned that explicitly.

      Delete
    2. Added the implementations to the last snippet.

      Delete
  2. This reminds me of Effective C++ Item 18: "Make interfaces easy to use correctly and hard to use incorrectly".

    http://my.safaribooksonline.com/book/programming/cplusplus/0321334876/designs-and-declarations/ch04lev1sec1

    ReplyDelete