Saturday 10 October 2009

Value Objects

Value Objects are those elements of domain model that do not have their own identity, only the data. Two instances of a particular Value Object are the same if they contain the same data.

In DDDSample all the Value Objects implement ValueObject interface which defines one operation
boolean sameValueAs(T other)
The implementation of the method checks whether the other object contains identical data. The equals implementation in value object uses internally the sameValueAs method.

In C# the context is a little bit different. We have some simple operator overloading and, by convention, all structs (like System.Decimal) and immutable classes (like System.String) have the equality operator overloaded. I find DDD's Value Objects very similar to these .NET stucts -- both are immutable and both are considered equal when contain the same atomic data.

We decided to make an exception in following Java DDDSample design in case of Value Objects and use standard .NET idioms to represent Value Objects. Our Value Objects define == and != overloads and we use them where sameValueAs is used in the original solution. I find this kind of code cleaner and more natural to .NET developers - the would tread Value Object instances like the simple classes they are familiar with.

To avoid code duplication in the comparison and hash code computation implementation, we created a base class for all Value Objects where we put the logic responsible for the calculations. This logic depends on the data contained by a concrete Value Object.

There is similar concept in original DDDSample experimental project (dddsample.domain.shared.experimental). It uses reflection to extract values from Value Object instance. Our approach is somewhat different. We decided to be explicit in what is and what is not considered vales contained by Vaule Object instance. We created a special protected abstract method
protected abstract IEnumerable<object> GetAtomicValues()
used by ValueObject base class methods to obtain a collection of contained data. We use another .NET idiom -- iterator blocks -- to make the code as readable as possible. For example, the RouteSpecification's GetAtomicValues implementation looks like this
protected override IEnumerable<object> GetAtomicValues()
{
   yield return _origin;
   yield return _destination;
   yield return _arrivalDeadline;
}
To sum up, we decided to drop the explicitness of comparing Value Objects which manifested itself in using special method instead of usual langugage mechanisms and, instead, use plain C#. It was possible because C# has some special features (i.e. operator overloading) which make this possible. I would argue that
trackingId == otherTrackingId
is more readable than
trackingId.sameValueAs(otherTrackingId)

No comments:

Post a Comment