According to Martin Fowler and other smart folks, Value Objects are immutable objects that are equal due to the value of their properties, as opposed to their identity. Common examples are small "fungible" objects like java.lang.Integer
, or compound objects whose overall equality is defined by the equality of the components.
I am curious if immutable collections (like lists, sets, or maps) of immutable objects can be generally considered Value Objects as well. For example, if two separate immutable list objects contain the same objects in the same order, most implementations would consider the two lists equal despite being different objects (e.g., com.google.common.collect.ImmutableList
, org.organicdesign.fp.collections.PersistentHashSet
). This seems to hold true independent of whether the collection itself contains Value Objects or just regular (non-Value) immutable objects. Even if the collection contains elements that are clearly not Value Objects (e.g., closures or lambdas), two lists would still be considered equal, despite the fact that their components are compared by identity.
Is my above reasoning correct or are there certain immutable collections (of immutable objects) that could not be considered Value Objects? To be clear, I am asking this question from a pure OOP, DDD and Design Patterns perspective. I am aware that "Value Object" has other implications, for example in an ORM context, where it might affect how things are stored in a database. Also, my question assumes a purely functional background where all objects are immutable.
CodePudding user response:
Yes and no.
There's quite a bit of overlap, but it's hardly perfect. Some immutable collections are just data structures, while the notion of a Value Object implies a degree of explicit Domain Modelling.
Even if the collection contains elements that are clearly not Value Objects (e.g., closures or lambdas), two lists would still be considered equal, despite the fact that their components are compared by identity.
Yes and no. This depends on the language. In languages like Java or C#, everything is comparable. If you want to compare two functions or lambdas, you can do so in these languages because everything is an object, and the Object
base class defines an equals
/Equals
method. Two functions will only be considered equal if they actually point to the same memory address.
This is not so in Haskell. Here, the capability to compare two values for equality is an abstraction like everything else. It's called the Eq
type class, but in this context, think of it as an interface. Only those types that are Eq
instances (~ 'implement that interface') can be compared. Functions and lambda expressions are not Eq
instances, so can't be compared. An attempt to do so wouldn't compile.
This extends to lists. In Haskell, the standard (linked) list is immutable and has structural equality, but only if the content type is an Eq
instance. You can compare two lists containing strings, or numbers, or Money
values, or User
values, as long as those types are Eq
instances. But you can't compare two lists of functions, because functions aren't Eq
instances.
I would say that this is illustrative in more OOP-focused languages as well. Structural equality is useful and desirable, but it doesn't mean that every immutable object with structural equality is a Value Object. As an example, raw lambda expressions aren't.
It's not clear cut, though, because you can sometimes refactor a design that involves raw lambda expressions to a design that has 'proper' objects. For example, a raw lambda expression that returns a Boolean value is a predicate, but you can also model predicates with the Specification pattern, in which case the Specification objects start to look like proper objects. Make the Specification implementations immutable and give them structural equality, and they start to look like Value Objects...
You can use immutable collections to define and implement Value Objects. I could easily imagine a Customer
class with a list of Address
objects. Do you consider two Customer
objects equal if they have the same constituent values, including the same list of Address
objects? Yes, I would do that.
Is an immutable collection of mutable objects a Value Object? Hardly.
CodePudding user response:
From a DDD prespective, it is irrelevant how a Value Object is implemented. As you said:
Value Objects is are immutable objects that are equal due to the value of their properties, as opposed to their identity
There is nothing in this definition that implies a specific implementation and implementing a value object with an immutable collection is just a an implementation detail.
The most common value object examples are Money, which can be implemented with two properties, one for the value and another for the currency and Address, where you store the different parts of the address in different fields, but all those fields together identify a specific address. In these examples, it doesn't matter if you store them in the DB with some identity key, "20 USDs" in row 1 will be the same as "20 USD" in row 100.
Having this in mind, you can easily find examples of value objects that can be implemented with collections. For example, you could define a Shape as a collection of Points or Coordinates, so that a triangle would be defined with 3 points and another collection with the same 3 points would be exactly the same shape, regarless of them being stored in another memory position or different rows in the DB. So, in this case, the value objects equality would depend on the collection containing the same points in the same order.
In summary, you can implement a Value Object with an immutable collection, but not all immutable collections are Value Objects by definition.