A failed experiment: improving the Builder pattern
Sometimes, you want to try something new. Like last week, when I wanted to implement a builder for a domain object. In Java, lest you wonder why the code samples in this post are so long.
The rationale for wanting builders for domain objects goes roughly like this. You want:
- domain objects that are never in an inconsistent state
- immutable domain objects, preferably
- to avoid 'telescoping' constructors' taking all combinations of optional fields on your object upfront
- a nice (fluent) API for building your domain objects
Granted, you could just use Scala's case classes with named parameters and call it a day. Alas, this was no such day.
On the shoulders of giants
Obviously the builder pattern has been belaboured by many who are greater than I am. In fact, the original Gang-of-Four description dates back to 1995.
But here we are in 2013. Let's say we want a domain object modeling pizza orders. The usual solution looks somewhat like this, using a static inner class to implement the builder:
<span class="kd">private</span> <span class="kt">int</span> <span class="n">size</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">pepperoni</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">chicken</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">mushroom</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">peppers</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">String</span> <span class="n">cheese</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">String</span> <span class="n">sauce</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">String</span> <span class="n">orderFor</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">Builder</span> <span class="nf">pizzaOrder</span><span class="o">(</span><span class="kt">int</span> <span class="n">size</span><span class="o">,</span> <span class="n">String</span> <span class="n">sauce</span><span class="o">,</span> <span class="n">String</span> <span class="n">orderFor</span><span class="o">)</span>
<span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">PizzaOrderOldStyle</span><span class="o">.</span><span class="na">Builder</span><span class="o">(</span><span class="n">size</span><span class="o">,</span> <span class="n">sauce</span><span class="o">,</span> <span class="n">orderFor</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">Builder</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">size</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">pepperoni</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">chicken</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">mushroom</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">peppers</span><span class="o">;</span>
<span class="n">String</span> <span class="n">cheese</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">String</span> <span class="n">sauce</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">String</span> <span class="n">orderFor</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">Builder</span><span class="o">(</span><span class="kt">int</span> <span class="n">size</span><span class="o">,</span> <span class="n">String</span> <span class="n">sauce</span><span class="o">,</span> <span class="n">String</span> <span class="n">orderFor</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">size</span> <span class="o">=</span> <span class="n">size</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">sauce</span> <span class="o">=</span> <span class="n">sauce</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">orderFor</span> <span class="o">=</span> <span class="n">orderFor</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">Builder</span> <span class="nf">withPepperoni</span><span class="o">()</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">pepperoni</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">Builder</span> <span class="nf">withChicken</span><span class="o">()</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">chicken</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">Builder</span> <span class="nf">withMushroom</span><span class="o">()</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">mushroom</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">Builder</span> <span class="nf">withPeppers</span><span class="o">()</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">peppers</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">Builder</span> <span class="nf">withCheese</span><span class="o">(</span><span class="n">String</span> <span class="n">cheese</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">cheese</span> <span class="o">=</span> <span class="n">cheese</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">PizzaOrderOldStyle</span> <span class="nf">build</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">PizzaOrderOldStyle</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="nf">PizzaOrderOldStyle</span><span class="o">(</span><span class="n">Builder</span> <span class="n">builder</span><span class="o">)</span> <span class="o">{</span>
<span class="n">size</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">size</span><span class="o">;</span>
<span class="n">pepperoni</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">pepperoni</span><span class="o">;</span>
<span class="n">chicken</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">chicken</span><span class="o">;</span>
<span class="n">mushroom</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">mushroom</span><span class="o">;</span>
<span class="n">peppers</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">peppers</span><span class="o">;</span>
<span class="n">cheese</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">cheese</span><span class="o">;</span>
<span class="n">sauce</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">sauce</span><span class="o">;</span>
<span class="n">orderFor</span> <span class="o">=</span> <span class="n">builder</span><span class="o">.</span><span class="na">orderFor</span><span class="o">;</span>
<span class="o">}</span>
// Omitted getters // Omitted equals/hashCode }
It's fairly straightforward. The constructor for PizzaOrderOldStyle
is private and takes an instance of the builder. In this constructor, the fields on the domain object are initialized to the values from the builder. Only Builder
can be instantiated by the user of the API and it takes the non-optional values directly. The with*()
methods on the builder expose a Fluent API by returning this
. Since the resulting domain object has no setters, it is effectively immutable after it is returned from build()
.
Building the domain object is now as simple as:
(I added a static convenience method pizzaOrder
to instantiate the builder)
Problems
While the above solution seems reasonable, it contains some flaws. The most obvious one is that this pattern just shifts mutability to the Builder
class. If you don't share builders across threads (which seems reasonable) we can live with this. What bothers me most is that we have three (!) locations where all the properties of the domain model are enumerated. First, the field definitions are repeated inside the Builder
class. Second, the private constructor copies each of domain model properties. Way too many places to screw up. In fact, I only found out through a unit test that I forgot to copy the cheese
property from the builder into the domain object.
Granted, there are several IDE plugins or other forms of code generators that can automate builder generation. Generating code, however, opens up a whole different can of worms.
Can we do better?
A feeble attempt
Seeing this problem got me thinking. What if the builder is just a façade on top of the domain object? It could use the fields of the domain object as 'intermediate' storage, without exposing the domain object as a whole before it is ready. Java allows non-static inner classes to mess with private state of the outer object. So that was a good starting point:
<span class="kd">private</span> <span class="kt">int</span> <span class="n">size</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">pepperoni</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">chicken</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">mushroom</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">boolean</span> <span class="n">peppers</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">String</span> <span class="n">cheese</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">String</span> <span class="n">sauce</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">String</span> <span class="n">orderFor</span><span class="o">;</span>
<span class="kd">private</span> <span class="nf">PizzaOrder</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// Prevent direct instantiation</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="n">Builder</span> <span class="nf">pizzaOrder</span><span class="o">(</span><span class="kt">int</span> <span class="n">size</span><span class="o">,</span> <span class="n">String</span> <span class="n">sauce</span><span class="o">,</span> <span class="n">String</span> <span class="n">orderFor</span><span class="o">)</span>
<span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="n">PizzaOrder</span><span class="o">().</span><span class="na">new</span> <span class="n">Builder</span><span class="o">(</span><span class="n">size</span><span class="o">,</span> <span class="n">sauce</span><span class="o">,</span> <span class="n">orderFor</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Builder</span> <span class="o">{</span>
<span class="kd">private</span> <span class="n">AtomicBoolean</span> <span class="n">build</span> <span class="o">=</span> <span class="k">new</span> <span class="n">AtomicBoolean</span><span class="o">(</span><span class="kc">false</span><span class="o">);</span>
<span class="kd">public</span> <span class="nf">Builder</span><span class="o">(</span><span class="kt">int</span> <span class="n">_size</span><span class="o">,</span> <span class="n">String</span> <span class="n">_sauce</span><span class="o">,</span> <span class="n">String</span> <span class="n">_orderFor</span><span class="o">)</span> <span class="o">{</span>
<span class="n">size</span> <span class="o">=</span> <span class="n">_size</span><span class="o">;</span>
<span class="n">sauce</span> <span class="o">=</span> <span class="n">_sauce</span><span class="o">;</span>
<span class="n">orderFor</span> <span class="o">=</span> <span class="n">_orderFor</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">Builder</span> <span class="nf">withPepperoni</span><span class="o">()</span> <span class="o">{</span>
<span class="n">throwIfBuild</span><span class="o">();</span>
<span class="n">pepperoni</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">Builder</span> <span class="nf">withChicken</span><span class="o">()</span> <span class="o">{</span>
<span class="n">throwIfBuild</span><span class="o">();</span>
<span class="n">chicken</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">Builder</span> <span class="nf">withMushroom</span><span class="o">()</span> <span class="o">{</span>
<span class="n">throwIfBuild</span><span class="o">();</span>
<span class="n">mushroom</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">Builder</span> <span class="nf">withPeppers</span><span class="o">()</span> <span class="o">{</span>
<span class="n">throwIfBuild</span><span class="o">();</span>
<span class="n">peppers</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">Builder</span> <span class="nf">withCheese</span><span class="o">(</span><span class="n">String</span> <span class="n">_cheese</span><span class="o">)</span> <span class="o">{</span>
<span class="n">throwIfBuild</span><span class="o">();</span>
<span class="n">cheese</span> <span class="o">=</span> <span class="n">_cheese</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">PizzaOrder</span> <span class="nf">build</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">build</span><span class="o">.</span><span class="na">compareAndSet</span><span class="o">(</span><span class="kc">false</span><span class="o">,</span> <span class="kc">true</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">// check consistency here... </span>
<span class="k">return</span> <span class="n">PizzaOrder</span><span class="o">.</span><span class="na">this</span><span class="o">;</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="n">IllegalStateException</span><span class="o">(</span><span class="s">"Build may only be called once!"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">throwIfBuild</span><span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">build</span><span class="o">.</span><span class="na">get</span><span class="o">())</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="n">IllegalStateException</span><span class="o">(</span><span class="s">"Cannot modify builder after calling build()"</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
// Omitted getters // Omitted equals/hashCode }
Interestingly, the exposed API is identical since we again offer a static convenience function pizzaOrder
hiding the slightly funky new PizzaOrder().new Builder(..)
that is necessary to instantiate the builder:
Even though from a syntactic standpoint the API hasn't changed, from a usage perspective there are differences. After a call to build()
any other call on the builder results in an exception. This must be the case, since the underlying fields are the actual fields of the domain object. We don't want those to change after the builder is done. I used an AtomicBoolean
to 'seal' the builder and the underlying domain object. Compare this with the original approach, where you can build as many times as you want with the same Builder
instance. Whether this is a good or bad thing is debatable. In practice it doesn't make much difference since you tend to use a builder only once anyway.
Builder failure?
So why do I call this a failed experiment? First of all, I expected this solution to be more concise than the original. It isn't. Check the linecounts in this gist. Indeed, the fields are not enumerated three times. On the other hand, we have to add bookkeeping to manage the state of the builder façade to each with*()
method. It's easy to forget the call to throwIfBuild
and if you do this threatens the immutability of the domain object.
Second, non-nested inner classes keep an implicit reference to their outer containing objects. This means that the builder object itself may prevent the domain object from being garbage-collected. Retaining a reference to the builder in the original pattern doesn't have this problem, since inner static classes are instantiated without an outer instance and hence don't point to an outer instance.
Pattern failure?
So the result is not groundbreaking. Still, it's a nice variation on the builder pattern. Here's the gist containing this post's code if you want to play around with it. More than anything, it reminds us that design patterns only serve to point out weaknesses in languages. Creating and maintaining builders for every domain object is just too much hassle. Languages like Scala and C# are much better equipped in this regard.
One improvement I've been thinking of is to use a nested empty instance of the domain class in the builder to store the data. We can gradually build up the object by modifying its private fields until we return it from build()
. In this variation the builder can be made static again. In fact, while writing this I decided to implement this variation and it looks like the cleanest approach so far.
Leave a comment if you see other improvements that I missed!