Value objects: Immutability and Equality
A couple of weeks ago I was working on some code where I wanted to create an object composed of the attributes of several other objects.
The object that I wanted to construct was a read only object so it seemed to make sense to make it a value object. The object would be immutable and once created none of the attributes of the object would change.
This was my first attempt at writing the code for this object:
public class MyValueObject
{
private readonly string otherValue;
private readonly SomeMutableEntity someMutableEntity;
public MyValueObject(SomeMutableEntity someMutableEntity, string otherValue)
{
this.someMutableEntity = someMutableEntity;
this.otherValue = otherValue;
}
public string SomeValue { get { return someMutableEntity.SomeValue; } }
public int SomeOtherValue {get { return someMutableEntity.SomeOtherValue; }}
public string OtherValue { get { return otherValue; } }
public bool Equals(MyValueObject obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return Equals(obj.OtherValue, OtherValue) && Equals(obj.SomeOtherValue, SomeOtherValue) && Equals(obj.SomeValue, SomeValue);
}
public override bool Equals(object obj)
{
// other equals stuff here
}
}
It wasn’t immediately obvious to me what the problem was with this solution but it felt really weird to be making use of properties in the equals method.
After discussing this strangeness with Dave he pointed out that 'MyValueObject' is not immutable because although the reference to 'SomeMutableEntity' inside the object cannot be changed the object itself had lots of setters and could therefore be changed from outside this class.
There are two ways to get around this problem:
-
We still inject 'SomeMutableEntity' but we extract the values from it in the constructor and don’t keep a reference to it.
-
The client that creates 'MyValueObject' constructs the object with the appropriate values
The first solution would work but it feels really weird to pass in the whole object when we only need a small number of its attributes - it’s a case of stamp coupling.
It also seems quite misleading as an API because it suggests that 'MyValueObject' is made up of 'SomeMutableEntity' which isn’t the case.
My preferred solution is therefore to allow the client to construct 'MyValueObject' with all the parameters individually.
public class MyValueObject
{
private readonly string otherValue;
private readonly int someOtherValue;
private readonly string someValue;
public MyValueObject(string someValue, string otherValue, int someOtherValue)
{
this.someValue = someValue;
this.otherValue = otherValue;
this.someOtherValue = someOtherValue;
}
public string SomeValue { get { return someValue; } }
public int SomeOtherValue { get { return someOtherValue; } }
public string OtherValue { get { return otherValue; } }
}
The constructor becomes a bit more complicated but it now feels a bit more intuitive and 'MyValueObject' lives up to its role as a value object.
The equals method can now just compare equality on the fields of the object.
public class MyValueObject
{
...
public bool Equals(MyValueObject obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return Equals(obj.otherValue, otherValue) && Equals(obj.someValue, someValue) && obj.someOtherValue == someOtherValue;
}
}
A client might create this object like so:
var myValueObject = new MyValueObject(someMutableEntity.SomeValue, otherValue, someMutableEntity.SomeOtherValue);
About the author
I'm currently working on short form content at ClickHouse. I publish short 5 minute videos showing how to solve data problems on YouTube @LearnDataWithMark. I previously worked on graph analytics at Neo4j, where I also co-authored the O'Reilly Graph Algorithms Book with Amy Hodler.