Skip to main content

Value Object (Software Design)

In Object-Oriented Programming, a Value Object is an abstraction of a value belonging to a particular domain.

A Value Object usually provides access to operations on the value given an OOP interface, or through operator overloading (e.g., concatenating strings with +).

A Value Object can represent a primitive datatype such as integer, float or boolean as well as more complex data, where the value of the particular Value Object is defined through more than one field (e.g., an address).

Key aspects of a Value Object

Fowler introduces the Value Object in [📖POEAA, p. 486] as an object "whose equality isn't based on identity".
In [📖DDD, p. 97], Evans refers to a Value Objects as an object that "describe[s] some characteristic of a thing".
Ward Cunningham relates the Whole Value (a.k.a. Value Object) to a concept that represents not a thing, "but measures of things".

All authors seem to agree on the statement that a Value Object is not a thing, but measure of it, and lay foundation to key design aspects of aValue Object with: Equality, Shareability, Immutability.

Values as Objects

Treating values as objects is already fundamental to many programming languages, such as Java, where the class java.lang.String is implemented as an immutable Value Object:

"The String class represents character strings. [...] Strings are constant; their values cannot be changed after they are created. [...] Because String objects are immutable they can be shared." [java.lang.String]

Identity

When designing abstractions of values, there is no need to consider the identity of a value, whereas identity in this context refers to the abstraction of an identifier available with the high-level code, such as a field id, containing an unique identifier. Such unique identifiers usually help with referring to objects that have a lifecycle, allowing to track the state of these objects, and furthermore reconstitute it through various instances of a software system.

Value Objects do not have such an identity, since their identity is of no interest to the client, as their inherent value is solely used for measurement or description.

A money value may exist to describe the financial worth of something. An address value may exist to describe the residence of a person. If the person moves, the address of the person moves with this person. If the financial worth of a company changes, the value reflecting the worth gets updated. In both cases, these values do not have to be uniquely identifiable.

Is Address a Value Object?

I provide a use case where Address as Value Object is meaningful iin the given context. Also, Eric Evans shares the notion that it depends on "who's asking" and reflects on several use cases in [📖DDD, p. 98].

Picking an apple

If I want to pick a green apple out of a set of green and red apples, I don't care about the identity of the apple, for as long as the apple can be described as green. If I put the apple away, I can always return later and pick another one from the set, preferably one that's green; I don't care if it turns out to be the same one or not.

Equality

If equality isn't based on identity, then equality is determined based on one or more attributes between objects.

In Java, comparing for the equality of Strings is done using the equals-method:

String name = new String("Peter");
String firstname = new String("Peter");

// name == firstname -> false
// name.equals(firstname) -> true

Here, name and firstname are equal, but not identical. When we speak of identical in this context, we refer to the low-level identity of two variables referencing the same memory-address where an object is stored.

equals() in Java

In Java, java.lang.Object provides a method for implementing equivalence relations on references:

public boolean equals(Object obj);

Specifics of java.lang.Object can implement equals to consider attribute-values (equality based on values) and references when testing for equality. [Java Docs]

Value Objects are used in cases where the identity of objects is not important, but their equality. In the given example, the value of the String-objects are of interest when comparing them for equality, but not their identity.

The identity and the equality of Value Objects are based on the values of their attributes, conversely to an Entity, which can be treated non-equal even if attributes are the same for other Entities. This is often realized through some kind of identifier: If two Entities both share the same value name="Peter Parker", but do not share the same value for their unique identifier, then these Entities should not be considered equal.

In the following example, a system requires a model of an Employee. Clearly, the identity of Employee cannot be based on the Employee's name, since the same name may occur multiple times in a system. The model needs an unique id, so Employees can be distinguished in a system. Then, the equality is based on identity: An object is only equal to another object if it shares the same reference or if the id is the same, which can only exist one at a time in a system.


class Employee
{
private string $name;
private int $id;
public function __construct(int $id, string $name)
{
$this->id = $id;
$this->name = $name;
}
}

$personOne = new Employee(1, "Parker");
$personTwo = new Employee(2, "Parker");

The system can now reconcile necessary processes with the Person-Data: If "Parker" is about to be transferred to another department, it is important to identify the right "Parker", so the wrong one does not end up in a position he wasn't qualified for in the first place.

Value Objects are next to Entities building blocks of Aggregates in DDD. They are often designed as immutable objects.

Immutability

I follow the notion that Value Objects should be immutable.

Figure 1 Does adding 24 to 5776 change 5776 to 5800?

If 24N24 \in \N is added to 5776N5776 \in \N, the number 57765776 still exists as an element in N\N: It does not get accidentally changed to 58005800, because if that would be the case, anything that previously referred to 57765776 would now unintentionally refer to 58005800 (also, we might run out of numbers in the not so near future). Instead, adding one value to another one yields a new value, reflecting the sum of both values - in this case 5800N5800 \in \N.

Figure 1 5776 is an element in N. Adding a new value to it does not change 5776, but instead returns a new value, 5800
A (simplified) Value Object for Integer
class IntegerValue {

static from (value) {
return new IntegerValue(value);
}

constructor(value) {
this.v = value;
}

value () {
return this.v;
}

add (integerValue) {
return IntegerValue.from(this.value() + integerValue.value());
}

}

const i24 = IntegerValue.from(24);
const i5776 = IntegerValue.from(5776);
const i5800 = i5776.add(i24);

console.log(i5800.value());
The One-nNn \in \N Universe

"Feynman, I know why all electrons have the same charge and the same mass" "Why?" "Because, they are all the same electron!" ["The Development of the Space-Time View of Quantum Electrodynamics"]

Based on the postulate of the One-Electron Universe, we can also postulate that all xNx \in \N are manifestations of one and the same nNn \in \N travelling forth and back in time. We'd just have a hard time to provide an accurate implementation of this model in software.

Sharing

Value Objects can easily be used as shared instances for a number of referencing objects. If implemented as immutable and the value of an object-defining attribute needs to change, the targeted instance gets replaced with a new instance, containing the updated state of the attribute(s). This makes sure that a change to one Value Object does not affect all objects referencing this particular Value Object.

Aliasing Bugs

If mutable Value Objects are shared, Aliasing Bugs are likely to happen. Making them immutable prevents these kind of errors.

Example

Money

In most of our computer programs we do not care about the identity of Money, but the value representing its amount.

The following example implements a Money-class (see [📖POEAA, pp. 488-495] for a more elaborate example of the design for Money). As an immutable Value Object, we can not change its value directly. Instead, a new instance of Money is returned each time we add() an amount to an instance, preventing Aliasing Bugs.

In Account.py, each Employee has initially the same salary. This salary is a shared instance of Money. If Moneygets added to an Employee's Account, a new instance of Money is created for the Account-instance.

Changing the value of salary does not affect the Accounts initialized with this salary.

    class Money:
def __init__(self, value = 0):
self.value = value

def getValue(self):
return self.value;

def add(self, money: Self) -> Self:
return Money(self.value + money.getValue())

see also