Aspects of value objects design

In the last chapter, we examined the anatomy of a value object. Still, there are several more aspects of value objects design that I still need to mention to give you a full picture.

Immutability

I mentioned before that value objects are usually immutable. Some say immutability is the core part of something being a value (e.g. Kent Beck goes as far as to say that 1 is always 1 and will never become 2), while others don’t consider it as a hard constraint. One way or another, designing value objects as immutable has served me exceptionally well to the point that I don’t even consider writing value object classes that are mutable. Allow me to describe three of the reasons I consider immutability a key constraint for value objects.

Accidental change of hash code

Many times, values are used as keys in hash maps (.e.g .NET’s Dictionary<K,V> is essentially a hash map). Let’s imagine we have a dictionary indexed with instances of a type called KeyObject:

1 Dictionary<KeyObject, AnObject> _objects;

When we use a KeyObject to insert a value into a dictionary:

1 KeyObject key = ...
2 _objects[key] = anObject;

then its hash code is calculated and stored separately from the original key.

When we read from the dictionary using the same key:

1 AnObject anObject = _objects[key];

then its hash code is calculated again and only when the hash codes match are the key objects compared for equality.

Thus, to successfully retrieve an object from a dictionary with a key, this key object must meet the following conditions regarding the key we previously used to put the object in:

  1. The GetHashCode() method of the key used to retrieve the object must return the same hash code as that of the key used to insert the object did during the insertion,
  2. The Equals() method must indicate that both the key used to insert the object and the key used to retrieve it are equal.

The bottom line is: if any of the two conditions is not met, we cannot expect to get the item we inserted.

I mentioned in the previous chapter that the hash code of a value object is calculated based on its state. A conclusion from this is that each time we change the state of a value object, its hash code changes as well. So, let’s assume our KeyObject allows changing its state, e.g. by using a method SetName(). Thus, we can do the following:

1 KeyObject key = KeyObject.With("name");
2 _objects[key] = new AnObject();
3 
4 // we mutate the state:
5 key.SetName("name2");
6 
7 //do we get the inserted object or not?
8 var objectIInsertedTwoLinesAgo = _objects[key];

This will throw a KeyNotFoundException (this is the dictionary’s behavior when it is indexed with a key it does not contain), as the hash code when retrieving the item is different than it was when inserting it. By changing the state of the key with the statement: key.SetName("name2");, I also changed its calculated hash code, so when I asked for the previously inserted object with _objects[val], I tried to access an entirely different place in the dictionary than the one where my object is stored.

As I find it a quite common situation that value objects end up as keys inside dictionaries, I’d rather leave them immutable to avoid nasty surprises.

Accidental modification by foreign code

I bet many who code or coded in Java know its Date class. Date behaves like a value (it has overloaded equality and hash code generation), but is mutable (with methods like setMonth(), setTime(), setHours() etc.).

Typically, value objects tend to be passed a lot throughout an application and used in calculations. Many Java programmers at least once exposed a Date value using a getter:

 1 public class ObjectWithDate {
 2 
 3   private final Date date = new Date();
 4 
 5   //...
 6 
 7   public Date getDate() {
 8     //oops...
 9     return this.date;
10   }
11 }

The getDate() method allows users of the ObjectWithDate class to access the date. But remember, a date object is mutable and a getter returns a reference! Everyone who calls the getter gains access to the internally stored instance of Date and can modify it like this:

1 ObjectWithDate o = new ObjectWithDate();
2 
3 o.getDate().setTime(date.getTime() + 10000); //oops!
4 
5 return date;

Of course, almost no one would probably do it in the same line like in the snippet above, but usually, this date would be accessed, assigned to a variable and then passed through several methods, one of which would do something like this:

1 public void doSomething(Date date) {
2   date.setTime(date.getTime() + 10000); //oops!
3   this.nextUpdateTime = date;
4 }

This led to unpredicted situations as the date objects were accidentally modified far, far away from the place they were retrieved40.

As most of the time it wasn’t the intention, the problem of date mutability forced us to manually create a copy each time their code returned a date:

1 public Date getDate() {
2   return (Date)this.date.clone();
3 }

which many of us tended to forget. This cloning approach, by the way, may have introduced a performance penalty because the objects were cloned every time, even when the code that was calling the getDate() had no intention of modifying the date41.

Even when we follow the suggestion of avoiding getters, the same applies when our class passes the date somewhere. Look at the body of a method, called dumpInto():

1 public void dumpInto(Destination destination) {
2   destination.write(this.date); //passing reference to mutable object
3 }

Here, the destination is allowed to modify the date it receives anyway it likes, which, again, is usually against developers’ intentions.

I saw many, many issues in production code caused by the mutability of Java Date type alone. That’s one of the reasons the new time library in Java 8 (java.time) contains immutable types for time and date. When a type is immutable, you can safely return its instance or pass it somewhere without having to worry that someone will overwrite your local state against your will.

Thread safety

When mutable values are shared between threads, there is a risk that they are changed by several threads at the same time or modified by one thread while being read by another. This can cause data corruption. As I mentioned, value objects tend to be created many times in many places and passed inside methods or returned as results a lot - this seems to be their nature. Thus, this risk of data corruption or inconsistency raises.

Imagine our code took hold of a value object of type Credentials, containing username and password. Also, let’s assume Credentials objects are mutable. If so, one thread may accidentally modify the object while it is used by another thread, leading to data inconsistency. So, provided we need to pass login and password separately to a third-party security mechanism, we may run into the following:

1 public void LogIn(Credentials credentials)
2 {
3   thirdPartySecuritySystem.LogIn(
4     credentials.GetLogin(),
5     //imagine password is modified before the next line
6     //from a different thread
7     credentials.GetPassword())
8 }

On the other hand, when an object is immutable, there are no multithreading concerns. If a piece of data is read-only, it can be safely read by as many threads as we like. After all, no one can modify the state of an object, so there is no possibility of inconsistency42.

If not mutability, then what?

For the reasons I described, I consider immutability a crucial aspect of value object design and in this book, when I talk about value objects, I assume they are immutable.

Still, there is one question that remains unanswered: what about a situation when I need to:

  • replace all occurrences of letter ‘r’ in a string to letter ‘l’?
  • move a date forward by five days?
  • add a file name to an absolute directory path to form an absolute file path (e.g. “C:" + “myFile.txt” = “C:\myFile.txt”)?

If I am not allowed to modify an existing value, how can I achieve such goals?

The answer is simple - value objects have operations that, instead of modifying the existing object, return a new one, with the state we are expecting. The old value remains unmodified. This is the way e.g. strings behave in Java and C#.

Just to address the three examples I mentioned

  • when I have an existing string and want to replace every occurence of letter r with letter l:
1 string oldString = "rrrr";
2 string newString = oldString.Replace('r', 'l');
3 //oldString is still "rrrr", newString is "llll"
  • When I want to move a date forward by five days:
1 DateTime oldDate = DateTime.Now;
2 DateTime newString = oldDate + TimeSpan.FromDays(5);
3 //oldDate is unchanged, newDate is later by 5 days
  • When I want to add a file name to a directory path to form an absolute file path43:
1 AbsoluteDirectoryPath oldPath 
2   = AbsoluteDirectoryPath.Value(@"C:\Directory");
3 AbsoluteFilePath newPath = oldPath + FileName.Value("file.txt");
4 //oldPath is "C:\Directory", newPath is "C:\Directory\file.txt"

So, again, anytime we want to have a value based on a previous value, instead of modifying the previous object, we create a new object with the desired state.

Immutability gotchas

Watch out for the constructor!

Going back to the Java’s Date example - you may think that it’s fairly easy to get used to cases such as that one and avoid them by just being more careful, but I find it difficult due to many gotchas associated with immutability in languages such as C# or Java. For example, another variant of the Date case from Java could be something like this: Let’s imagine we have a Money type, which is defined as:

 1 public sealed class Money
 2 {
 3   private readonly int _amount;
 4   private readonly Currencies _currency;
 5   private readonly List<ExchangeRate> _exchangeRates;
 6 
 7   public Money(
 8     int amount,
 9     Currencies currency,
10     List<ExchangeRate> exchangeRates)
11   {
12     _amount = amount;
13     _currency = currency;
14     _exchangeRates = exchangeRates;
15   }
16 
17   //... other methods
18 }

Note that this class has a field of type List<>, which is itself mutable. But let’s also imagine that we have carefully reviewed all of the methods of this class so that this mutable data is not exposed. Does it mean we are safe?

The answer is: as long as our constructor stays as it is, no. Note that the constructor takes a mutable list and just assigns it to a private field. Thus, someone may do something like this:

1 List<ExchangeRate> rates = GetExchangeRates();
2 
3 Money dollars = new Money(100, Currencies.USD, rates);
4 
5 //modify the list that was passed to dollars object
6 rates.Add(GetAnotherExchangeRate());

In the example above, the dollars object was changed by modifying the list that was passed inside. To get the immutability, one would have to either use an immutable collection library or change the following line:

1 _exchangeRates = exchangeRates;

to:

1 _exchangeRates = new List<ExchangeRate>(exchangeRates);
Inheritable dependencies can surprise you!

Another gotcha has to do with objects of types that can be subclassed (i.e. are not sealed). Let’s take a look at the example of a class called DateWithZone, representing a date with a time zone. Let’s say that this class has a dependency on another class called ZoneId and is defined as such:

 1 public sealed class DateWithZone : IEquatable<DateWithZone>
 2 {
 3   private readonly ZoneId _zoneId;
 4 
 5   public DateWithZone(ZoneId zoneId)
 6   {
 7     _zoneId = zoneId;
 8   }
 9 
10   //... some equality methods and operators...
11 
12   public override int GetHashCode()
13   {
14     return (_zoneId != null ? _zoneId.GetHashCode() : 0);
15   }
16 
17 }

Note that for simplicity, I made the DateWithZone type consist only of zone id, which of course in reality does not make any sense. I am doing this only because I want this example to be stripped to the bone. This is also why, for the sake of this example, ZoneId type is defined simply as:

1 public class ZoneId
2 {
3 
4 } 

There are two things to note about this class. First, it has an empty body, so no fields and methods defined. The second thing is that this type is not sealed (OK, the third thing is that this type does not have value semantics, since its equality operations are inherited as reference-based from the Object class, but, again for the sake of simplification, let’s ignore that).

I just said that the ZoneId does not have any fields and methods, didn’t I? Well, I lied. A class in C# inherits from Object, which means it implicitly inherits some fields and methods. One of these methods is GetHashCode(), which means that the following code compiles:

1 var zoneId = new ZoneId();
2 Console.WriteLine(zoneId.GetHashCode());

The last piece of information that we need to see the bigger picture is that methods like Equals() and GetHashCode() can be overridden. This, combined with the fact that our ZoneId is not sealed, means that somebody can do something like this:

 1 public class EvilZoneId : ZoneId
 2 {
 3   private int _i = 0;
 4   
 5   public override GetHashCode()
 6   {
 7     _i++;
 8     return i;  
 9   }
10 }

When calling GetHashCode() on an instance of this class multiple times, it’s going to return 1,2,3,4,5,6,7… and so on. This is because the _i field is a piece of mutable state and it is modified every time we request a hash code. Now, I assume no sane person would write code like this, but on the other hand, the language does not restrict it. So assuming such an evil class would come to existence in a codebase that uses the DateWithZone, let’s see what could be the consequences.

First, let’s imagine someone doing the following:

1 var date = new DateWithZone(new EvilZoneId());
2 
3 //...
4 
5 DoSomething(date.GetHashCode());
6 DoSomething(date.GetHashCode());
7 DoSomething(date.GetHashCode());

Note that the user of the DateWithZone instance uses its hash code, but the GetHashCode() operation of this class is implemented as:

1 public override int GetHashCode()
2 {
3   return (_zoneId != null ? _zoneId.GetHashCode() : 0);
4 }

So it uses the hash code of the zone id, which, in our example, is of class EvilZoneId which is mutable. As a consequence, our instance of DateWithZone ends up being mutable as well.

This example shows a trivial and not too believable case of GetHashCode() because I wanted to show you that even empty classes have some methods that can be overridden to make the objects mutable. To make sure the class cannot be subclassed by a mutable class, we would have to either make all methods sealed (including those inherited from Object) or, better, make the class sealed. Another observation that can be made is that if our ZoneId was an abstract class with at least one abstract method, we would have no chance of ensuring immutability of its implementations, as abstract methods by definition exist to be implemented in subclasses, so we cannot make an abstract method or class sealed.

Another way of preventing mutability by subclasses is making the class constructor private. Classes with private constructors can still be subclassed, but only by nested classes, so there is a way for the author of the original class to control the whole hierarchy and make sure no operation mutates any state.

There are more gotchas (e.g. a similar one applied to generic types), but I’ll leave them for another time.

Handling of variability

As in ordinary objects, there can be some variability in the world of values. For example, money can be dollars, pounds, zlotys (Polish money), euros, etc. Another example of something that can be modeled as a value is path values (you know, C:\Directory\file.txt or /usr/bin/sh) – there can be absolute paths, relative paths, paths to files and paths pointing to directories, we can have Unix paths and Windows paths.

Contrary to ordinary objects, however, where we solved variability by using interfaces and different implementations (e.g. we had an Alarm interface with implementing classes such as LoudAlarm or SilentAlarm), in the world values we do it differently. Taking the alarms I just mentioned as an example, we can say that the different kinds of alarms varied in how they fulfilled the responsibility of signaling that they were turned on (we said they responded to the same message with – sometimes entirely – different behaviors). Variability in the world of values is typically not behavioral in the same way as in the case of objects. Let’s consider the following examples:

  1. Money can be dollars, pounds, zlotys, etc., and the different kinds of currencies differ in what exchange rates are applied to them (e.g. “how many dollars do I get from 10 Euros and how many from 10 Pounds?”), which is not a behavioral distinction. Thus, polymorphism does not fit this case.
  2. Paths can be absolute and relative, pointing to files and directories. They differ in what operations can be applied to them. E.g. we can imagine that for paths pointing to files, we can have an operation called GetFileName(), which doesn’t make sense for a path pointing to a directory. While this is a behavioral distinction, we cannot say that “directory path” and a “file path” are variants of the same abstraction - rather, that are two different abstractions. Thus, polymorphism does not seem to be the answer here either.
  3. Sometimes, we may want to have a behavioral distinction, like in the following example. We have a value class representing product names and we want to write in several different formats depending on the situation.

How do we model this variability? I usually consider three basic approaches, each applicable in different contexts:

  • implicit - which would apply to the money example,
  • explicit - which would fit the case of paths nicely,
  • delegated - which would fit the case of product names.

Let me give you a longer description of each of these approaches.

Implicit variability

Let’s go back to the example of modeling money using value objects44. Money can have different currencies, but we don’t want to treat each currency in any special way. The only things that are impacted by currency are rates by which we exchange them for other currencies. We want the rest of our program to be unaware of which currency it’s dealing with at the moment (it may even work with several values, each of different currency, at the same time, during one calculation or another business operation).

This leads us to make the differences between currencies implicit, i.e. we will have a single type called Money, which will not expose its currency at all. We only have to tell what the currency is when we create an instance:

1 Money tenPounds = Money.Pounds(10);
2 Money tenBucks = Money.Dollars(10);
3 Money tenYens = Money.Yens(10);

and when we want to know the concrete amount in a given currency:

1 //doesn't matter which currency it is, we want dollars.
2 decimal amountOfDollarsOnMyAccount = mySavings.AmountOfDollars();

other than that, we are allowed to mix different currencies whenever and wherever we like45:

1 Money mySavings =
2   Money.Dollars(100) +
3   Money.Euros(200) +
4   Money.Zlotys(1000);

This approach works under the assumption that all of our logic is common for all kinds of money and we don’t have any special piece of logic just for Pounds or just for Euros that we don’t want to pass other currencies into by mistake46.

To sum up, we designed the Money type so that the variability of currency is implicit - most of the code is simply unaware of it and it is gracefully handled under the hood inside the Money class.

Explicit variability

There are times, however, when we want the variability to be explicit, i.e. modeled using different types. Filesystem paths are a good example.

For starters, let’s imagine we have the following method for creating backup archives that accepts a destination path (for now as a string - we’ll get to path objects later) as its input parameter:

1 void Backup(string destinationPath);

This method has one obvious drawback - its signature doesn’t tell anything about the characteristics of the destination path, which begs some questions:

  • Should it be an absolute path, or a relative path. If relative, then relative to what?
  • Should the path contain a file name for the backup file, or should it be just a directory path and a filename will be given according to some kind of pattern (e.g. a word “backup” + the current timestamp)?
  • Or maybe the file name in the path is optional and if none is given, then a default name is used?

These questions suggest that the current design doesn’t convey the intention explicitly enough. We can try to work around it by changing the name of the parameter to hint the constraints, like this:

1 void Backup(string absoluteFilePath);

but the effectiveness of that is based solely on someone reading the argument name and besides, before a path (passed as a string) reaches this method, it is usually passed around several times and it’s very hard to keep track of what is inside this string, so it becomes easy to mess things up and pass e.g. a relative path where an absolute one is expected. The compiler does not enforce any constraints. More than that, one can pass an argument that’s not even a path, because a string can contain any arbitrary content.

It looks to me like a good situation to introduce a value object, but what kind of type or types should we introduce? Surely, we could create a single type called Path47 that would have methods like IsAbsolute(), IsRelative(), IsFilePath() and IsDirectoryPath() (i.e. it would handle the variability implicitly), which would solve (only - we’ll see that shortly) one part of the problem - the signature would be:

1 void Backup(Path absoluteFilePath);

and we would not be able to pass an arbitrary string, only an instance of a Path, which may expose a factory method that checks whether the string passed is in a proper format:

1 //the following could throw an exception
2 //because the argument is not in a proper format
3 Path path = Path.Value(@"C:\C:\C:\C:\//\/\/");

Such a factory method could throw an exception at the time of path object creation. This is important - previously, when we did not have the value object, we could assign garbage to a string, pass it between several objects and get an exception from the Backup() method. Now, that we modeled paths as value objects, there is a high probability that the Path type will be used as early as possible in the chain of calls. Thanks to this and the validation inside the factory method, we will get an exception much closer to the place where the mistake was made, not at the end of the call chain.

So yeah, introducing a general Path value object might solve some problems, but not all of them. Still, the signature of the Backup() method does not signal that the path expected must be an absolute path to a file, so one may pass a relative path or a path to a directory, even though only one kind of path is acceptable.

In this case, the varying properties of paths are not just an obstacle, a problem to solve, like in case of money. They are the key differentiating factor in choosing whether a behavior is appropriate for a value or not. In such a case, it makes a lot of sense to create several different value types, each representing a different set of path constraints.

Thus, we may decide to introduce types like48:

  • AbsoluteFilePath - representing an absolute path containing a file name, e.g. C:\Dir\file.txt
  • RelativeFilePath - representing a relative path containing a file name, e.g. Dir\file.txt
  • AbsoluteDirPath - representing an absolute path not containing a file name, e.g. C:\Dir\
  • RelativeDirPath - representing a relative path not containing a file name, e.g. Dir\

Having all these types, we can now change the signature of the Backup() method to:

1 void Backup(AbsoluteFilePath path);

Note that we don’t have to explain the constraints with the name of the argument - we can just call it path because the type already says what needs to be said. And by the way, no one will be able to pass e.g. a RelativeDirPath now by accident, not to mention an arbitrary string.

Making variability among values explicit by creating separate types usually leads us to introduce some conversion methods between these types where such conversion is legal. For example, when all we’ve got is an AbsoluteDirPath, but we still want to invoke the Backup() method, we need to convert our path to an AbsoluteFilePath by adding a file name, that can be represented by a value objects itself (let’s call its class FileName). In C#, we can use operator overloading for some of the conversions, e.g. the + operator would do nicely for appending a file name to a directory path. The code that does the conversion would then look like this:

1 AbsoluteDirPath dirPath = ...
2 ...
3 FileName fileName = ...
4 ...
5 //'+' operator is overloaded to handle the conversion:
6 AbsoluteFilePath filePath = dirPath + fileName;

Of course, we create conversion methods only where they make sense in the domain we are modeling. We wouldn’t put a conversion method inside AbsoluteDirectoryPath that would combine it with another AbsoluteDirectoryPath49.

Delegated variability

Finally, we can achieve variability by delegating the varying behavior to an interface and have the value object accept an implementation of the interface as a method parameter. An example of this would be the Product class from the previous chapter that had the following method declared:

1 public string ToString(Format format);

where Format was an interface and we passed different implementations of this interface to this method, e.g. ScreenFormat or ReportingFormat. Note that having the Format as a method parameter instead of e.g. a constructor parameter allows us to uphold the value semantics because Format is not part of the object but rather a “guest helper”. Thanks to this, we are free from dilemmas such as “is the name ‘laptop’ formatted for screen equal to ‘laptop’ formatted for a report?”

Summing up the implicit vs explicit vs delegated discussion

Note that while in the first example (the one with money), making the variability (in currency) among values implicit helped us achieve our design goals, in the path example it made more sense to do exactly the opposite - to make the variability (in both absolute/relative and to file/to directory axes) as explicit as to create a separate type for each combination of constraints.

If we choose the implicit approach, we can treat all variations the same, since they are all of the same type. If we decide on the explicit approach, we end up with several types that are usually incompatible and we allow conversions between them where such conversions make sense. This is useful when we want some pieces of our program to be explicitly compatible with only one of the variations.

I must say I find delegated variability a rare case (formatting the conversion to string is a typical example) and throughout my entire career, I had maybe one or two situations where I had to resort to it. However, some libraries use this approach and in your particular domain or type of application, such cases may be much more typical.

Special values

Some value types have values that are so specific that they have their own names. For example, a string value consisting of "" is called “an empty string”. 2,147,483,647 is called “a maximum 32 bit integer value”. These special values make their way into value objects design. For example, in C#, we have Int32.MaxValue and Int32.MinValue which are constants representing a maximum and minimum value of 32-bit integer and string.Empty representing an empty string. In Java, we have things like Duration.ZERO to represent a zero duration or DayOfWeek.MONDAY to represent a specific day of the week.

For such values, the common practice I’ve seen is making them globally accessible from the value object classes, as is done in all the above examples from C# and Java. This is because values are immutable, so the global accessibility doesn’t hurt. For example, we can imagine string.Empty implemented like this:

1 public sealed class String //... some interfaces here
2 {
3   //...
4   public const string Empty = "";
5   //...
6 }

The additional const modifier ensures no one will assign any new value to the Empty field. By the way, in C#, we can use const only for types that have literal values, like a string or an int. For our custom value objects, we will have to use a static readonly modifier (or static final in the case of Java). To demonstrate it, let’s go back to the money example from this chapter and imagine we want to have a special value called None to symbolize no money in any currency. As our Money type has no literals, we cannot use the const modifier, so instead, we have to do something like this:

1 public class Money
2 {
3   //...
4 
5   public static readonly
6     Money None = new Money(0, Currencies.Whatever);
7 
8   //...
9 }

This idiom is the only exception I know from the rule I gave you several chapters ago about not using static fields at all. Anyway, now that we have this None value, we can use it like this:

1 if(accountBalance == Money.None)
2 {
3   //...
4 }

Value types and Tell Don’t Ask

When talking about the “web of objects” metaphor, I stressed that objects should be told what to do, not asked for information. I also wrote that if a responsibility is too big for a single object to handle, it shouldn’t try to achieve it alone, but rather delegate the work further to other objects by sending messages to them. I mentioned that preferably we would like to have mostly void methods that accept their context as arguments.

What about values? Does that metaphor apply to them? And if so, then how? And what about Tell Don’t Ask?

First of all, values don’t appear explicitly in the web of objects metaphor, at least they’re not “nodes” in this web. Although in almost all object-oriented languages, values are implemented using the same mechanism as objects - classes50, I treat them as somewhat different kind of construct with their own set of rules and constraints. Values can be passed between objects in messages, but we don’t talk about values sending messages by themselves.

A conclusion from this may be that values should not be composed of objects (understood as nodes in the “web”). Values should be composed of other values (as our Path type had a string inside), which ensures their immutability. Also, they can occasionally accept objects as parameters of their methods (like the ProductName class from the previous chapter that had a method ToString() accepting a Format interface), but this is more of an exception than a rule. In rare cases, I need to use a collection inside a value object. Collections in Java and C# are not typically treated as values, so this is kind of an exception from the rule. Still, when I use collections inside value objects, I tend to use the immutable ones, like ImmutableList.

If the above statements about values are true, then it means values simply cannot be expected to conform to Tell Don’t Ask. Sure, we want them to encapsulate domain concepts, to provide higher-level interface, etc., so we struggle very hard for the value objects not to become plain data structures like the ones we know from C, but the nature of values is rather as “intelligent pieces of data” rather than “abstract sets of behaviors”.

As such, we expect values to contain query methods (although, as I said, we strive for something more abstract and more useful than mere “getter” methods most of the time). For example, you might like the idea of having a set of path-related classes (like AbsoluteFilePath), but in the end, you will have to somehow interact with a host of third-party APIs that don’t know anything about those classes. Then, a ToString() method that just returns internally held value will come in handy.

Summary

This concludes my writing on value objects. I never thought there would be so much to discuss as to how I believe they should be designed. For readers interested in seeing a state-of-the-art case study of value objects, I recommend looking at Noda Time (for C#) and Joda Time (for Java) libraries (or Java 8 new time and date API).