Introduction
The Bloch's Builder Pattern involves creating a builder class for the convenient creation of a target class. A builder consists of methods that set specific properties
on the target object, and returns the builder itself so that subsequent calls can be chained. An example use of a builder may be:
Window window = new WindowBuilder()
.Width(500)
.Height(300)
.Build();
To briefly illustrate how this is done, the WindowBuilder
class has roughly the following member definitions:
class WindowBuilder
{
Window window;
public WindowBuilder()
{
window = new Window();
}
public WindowBuilder Width(int width)
{
window.Width = width;
return this;
}
public WindowBuilder Height(int height)
{
window.Height = height;
return this;
}
public Window Build()
{
Window result = window;
window = null;
return result;
}
}
As you see, each method returning the builder itself enables chaining.
(This article introduces a new pattern of creating builders. Because this is a technique rather than a library, I suggest that you read the whole article to grasp
a thorough understanding of this concept. But of course, you can skip to the end to read the complete example code or download it, but the code might be a bit difficult
to understand this way.)
Problems with this design
Suppose that the class I used above has two properties, Width
and Height
. Suppose now that I have a subclass of the class Window
, called
Dialog
. The Dialog
class has a new property called Message
, presumably storing the message displayed by the dialog.
The problem arises when I try to create an analogous DialogBuilder
. I would have to rewrite the builder methods Width()
and Height()
.
While this doesn't sound too bad, imagine creating a builder for a WinForms component such as Button
, which has many properties. We certainly don't want to write a builder
method for every single property each time we have a subclass.
A Parallel Hierarchy
By the term "Parallel Hierarchy", I'm referring to a similar inheritance structure between the builders and their target classes. Specifically, if Dialog
inherits from Window
, then DialogBuilder
shall inherit from WindowBuilder
. This also suggests that DialogBuilder
should take advantage
of existing methods in WindowBuilder
, namely Width()
and Height()
, so that DialogBuilder
does not have to re-implement these.
The first problem arises: The return type of Width()
and Height()
. They return WindowBuilder
. This means that after I call
Width()
, I will no longer be able to build a Dialog
because I get a WindowBuilder
in return. However, WindowBuilder
has no way of knowing that it should return DialogBuilder
instead.
The solution here is Generics. Let us modify WindowBuilder
like the following:
class WindowBuilder<T> where T : WindowBuilder<T>
{
public T Width(int width)
{
return (T)this;
}
public T Height(int height)
{
return (T)this;
}
}
Let me explain what this means. WindowBuilder
now takes a type argument, T
, which shall refer to the type of the actual builder we'll be using.
Then the property-setting methods will return T
, enabling the chaining on the actual builder. We place a restriction on T
that it must be a subclass
of WindowBuilder
(or itself). This restriction is important because it prevents the compiler from complaining when we convert this
to T
upon returning. (There is potentially a downcasting performance issue here, but this should rarely matter, considering the situations where a builder
would be used. But if you're concerned, we'll look into that later in this article.)
Note here that we've omitted the actual builder logic, just for clarity.
Now we'll look at the DialogBuilder
to see how a builder should inherit another:
class DialogBuilder<T> : WindowBuilder<T> where T : DialogBuilder<T>
{
public T Message(string message)
{
return (T)this;
}
}
Again, the builder logic code is omitted to emphasize on the inheritance pattern. Here the class definition on the first line looks very convoluted. Let me explain.
Except for the tokens ": WindowBuilder<T>
", everything else is just analogous to the WindowBuilder
declaration. We want DialogBuilder
to be a subclass of WindowBuilder
, and we pass the type parameter T
to the base class to tell the base class what builder it should return.
We also restrict T
to be a subclass of DialogBuilder
, both to enable casting in (T)this
, and to ensure that this class is used correctly.
Instantiating the builder
We have arrived at an interesting issue: How are we going to instantiate the builder? I've mentioned that T
refers to the type of the actual builder,
but to build a Dialog
, for example, what is the type of the actual builder? It can't be DialogBuilder
because we lack an argument.
It can't be DialogBuilder<DialogBuilder>
since we still lack an argument. Alas, it seems like we need a class called
DialogBuilder<DialogBuilder<DialogBuilder<...>>>
, but that's impossible. So here is the workaround:
class DialogBuilder : DialogBuilder<DialogBuilder>{ }
Wow wow, hold on. What is this mess? Well, the code above takes advantage of a feature in C# that DialogBuilder
and DialogBuilder<T>
would refer
to two entirely different classes. Calling the new class DialogBuilder
here is merely for neatness and to avoid namespace pollution. Notice how
the new DialogBuilder
class satisfies the condition on T
, that is a subclass of DialogBuilder<T>
! This means that we can now instantiate
the builder as follows:
new DialogBuilder()
This is neat because it hides from the user all the messy Generics. Next, we'll look at implementing the builder logic.
The builder logic
So far we've been focusing on the parallel inheritance of builders, but we missed an important aspect: to actually make the builder build something.
The original builder on the very top of this article created a Window
object upon initialization of the builder, and modified the Window
object
as the user called the builder methods. I don't intend to mimic the same style here, because I would be creating a Window
object and a Dialog
object
upon instantiation of DialogBuilder
. Alternatively, I can create a BuilderBase
class which contains an object
referring to the actual
object being built, but then every call to modify this object would involve a cast. Neither of these is very efficient.
The solution, again, is Generics.
We will add a generic argument to the builders, which indicate which class we are building. In this case, Window
or Dialog
. To take care of other
builders we may create, let's create a BuilderBase
class as follows:
class BuilderBase<T, P> where T : BuilderBase<T, P> where P : class, new()
{
protected P obj;
protected BuilderBase()
{
obj = new P();
}
public P Build()
{
P result = obj;
obj = null;
return result;
}
}
The added type argument P
refers to the class being built. It is restricted to be a class
(there is no point to support this kind of builders
for struct
s because struct
s cannot inherit from each other), and it must support the default parameter-less constructor.
A builder usually instantiates the target object with the default constructor, because alternate constructors should be used without a builder. So it's safe to assume
that our target class supports the default constructor. The case where the target class is abstract is not a problem, because P
will never be an abstract class anyway.
Builders for abstract classes can indeed be implemented. (We'll soon see how in this article.)
The BuilderBase
class keeps a reference to the object being built of type P
. It has a Build()
method that gives the object
and sets it to null
to prevent accidental duplicate calls of Build()
.
Now let's look at the new functional WindowBuilder
:
class WindowBuilder<T, P> : BuilderBase<T, P>
where T : WindowBuilder<T, P> where P : Window, new()
{
public T Width(int width)
{
obj.Width = width;
return (T)this;
}
public T Height(int height)
{
obj.Height = height;
return (T)this;
}
}
We've changed this class to inherit from BuilderBase<T, P>
, passing P
along. T
is now naturally restricted to subclass
WindowBuilder<T, P>
, and P
must be a subclass of Window
and can be instantiated with the default constructor.
Despite the mess on the class declaration, the body of the class is very simple. We simply set the corresponding properties on the object, which must be a subclass
of Window
, then return the builder itself.
Now let's look at the DialogBuilder
:
class DialogBuilder<T, P> : WindowBuilder<T, P>
where T : DialogBuilder<T, P> where P : Dialog, new()
{
public T Message(string message)
{
obj.Message = message;
return (T)this;
}
}
The builder class declaration is analogous every time, even for abstract classes. The distinction between abstract class builders and non-abstract class builders
is that abstract class builders don't (and can't) implement the following:
class WindowBuilder : WindowBuilder<WindowBuilder, Window> { }
class DialogBuilder : DialogBuilder<DialogBuilder, Dialog> { }
We now have a complete and working set of builders for the Window
and Dialog
classes. Example invocations are:
Window window = new WindowBuilder()
.Width(500)
.Height(400)
.Build();
Dialog dialog = new DialogBuilder()
.Width(500)
.Message("Hello")
.Height(400)
.Build();
Getting rid of casts
Let us revisit the statement:
return (T)this;
ILDASM shows that this statement does involve the unbox.any
instruction, which for a reference type is the same as a castclass
instruction.
This instruction can be potentially slow. For the sake of perfection, let's get rid of this cast (or at least, cast only once).
Add a reference to the BuilderBase
called _this
of type T
, and assign to this reference inside the constructor:
class BuilderBase<T, P> where T : BuilderBase<T, P> where P : class, new()
{
protected P obj;
protected T _this;
protected BuilderBase()
{
obj = new P();
_this = (T)this;
}
public P Build()
{
P result = obj;
obj = _this = null;
return result;
}
}
This seems like the best we can do, because we cannot inherit from a type argument T
, so this
can never be implicitly cast to T
.
Notice that I nullified _this
upon Build()
; perhaps this is unnecessary but it gets rid of all references to the builder,
making it easier for the garbage collector. I'm not sure of this but an extra null
assignment doesn't hurt. (If you know about this, please tell me about it. Thanks!)
Then we proceed to change every return
statement to the following:
return _this;
The complete code
Here it is, the complete code in this Window
and Dialog
example above:
using System;
using System.Collections.Generic;
using System.Text;
namespace HPMV.Builders
{
class TestProgram
{
static void Main(string[] args)
{
Window window = new WindowBuilder()
.Width(500)
.Height(400)
.Build();
Dialog dialog = new DialogBuilder()
.Width(500)
.Message("Hello")
.Height(400)
.Build();
}
}
class BuilderBase<T, P>
where T : BuilderBase<T, P>
where P : class, new()
{
protected P obj;
protected T _this;
protected BuilderBase()
{
obj = new P();
_this = (T)this;
}
public P Build()
{
P result = obj;
obj = null;
return result;
}
}
class Window
{
public int Width { get; set; }
public int Height { get; set; }
}
class Dialog : Window
{
public string Message { get; set; }
}
class WindowBuilder<T, P> : BuilderBase<T, P>
where T : WindowBuilder<T, P>
where P : Window, new()
{
public T Width(int width)
{
obj.Width = width;
return _this;
}
public T Height(int height)
{
obj.Height = height;
return _this;
}
}
class WindowBuilder : WindowBuilder<WindowBuilder, Window> { }
class DialogBuilder<T, P> : WindowBuilder<T, P>
where T : DialogBuilder<T, P>
where P : Dialog, new()
{
public T Message(string message)
{
obj.Message = message;
return _this;
}
}
class DialogBuilder : DialogBuilder<DialogBuilder, Dialog> { }
}
More builders
By following the pattern in the example code, we can make new builders quite easily. The only complex effort here is writing the class declaration correctly,
but a few tries and you should get used to it. Of course, you may also create a snippet or something similar, but I won't go into that in this article.
I hope you have found this builder pattern interesting and useful.
Credits
The parallel hierarchy pattern was observed in a recent build of JavaFX 2.0 Beta in the javafx.builders
package. Because of the difference
in the way Java and C# deal with Generics, the exact technique was impossible to apply to C#. The technique in this article is original, inspired
by the aforementioned JavaFX API.
History
- August 15, 2011: First version.