Home Design Patterns Builder
Builder
Creational Gang of Four“Separate the construction of a complex object from its representation so that the same construction process can create different representations.”
Gamma, Helm, Johnson & Vlissides, p. 97Purpose
Builder splits "what the steps are" from "what the steps produce". One class, the Director, knows the order of assembly and nothing about the result. Another class, the Builder, receives the steps and decides what to make of them. Run the same director over three different builders and you get three different outputs from one traversal.
It is worth saying early that two different patterns share this name. The one
in the book is the director-and-builder arrangement above, and it is genuinely
uncommon. The one most people mean today is the fluent builder, where an object
with many optional fields gets a companion class you chain setter calls on before
calling build(). They solve different problems and only the first
one is on this page's diagram. I cover the second in the criticisms, because it
deserves the space more than it deserves the confusion.
Design
The Builder interface declares one method per part, plus a way to retrieve the finished product. The Director holds a Builder and implements the assembly sequence once, calling those methods in whatever order and however many times the input demands. Concrete builders accumulate whatever internal state they need and answer with their own result type at the end.
getResult() is the awkward one. Each
concrete builder returns a different type, so in most languages that method
cannot honestly live on the shared interface, and the client has to know which
builder it handed over.The Director is the part people drop first, and often correctly. It only earns its keep when the assembly sequence is itself non-trivial and shared. If the sequence is "call these four setters", the Director is a class that adds nothing but a layer.
Example
An Autobot Robotics customer writes a job program once and it has to come out three ways: as the opcode stream the embedded controller executes, as a printed travel sheet the operator follows during setup, and as a model the offline simulator runs before anything touches real hardware. Same job, three representations.
These started life as three separate pieces of code that each walked the job
definition. They drifted, as three copies of a traversal always do. The one that
mattered was dwell: the simulator treated a dwell as time after the
move completed, and the controller started the timer when the move command was
issued. Programs validated clean in simulation and then collided with a fixture
on the floor, because in the real machine the arm was still moving when the clock
started.
The traversal is now written once, in a JobWalker, and there are
three builders behind it. The disagreement about dwell became impossible to
express, because there is no longer more than one place that decides what a dwell
step is. That is the property worth paying for here, more than the reduction in
code.
interface JobBuilder
{
void beginJob(string name);
void addMove(Point target, int speed);
void addDwell(int milliseconds);
void endJob();
}
class JobWalker // the Director
{
JobBuilder builder;
public JobWalker(JobBuilder b) { builder = b; }
public void walk(JobDefinition job)
{
builder.beginJob(job.Name);
foreach (Step s in job.Steps)
{
if (s.IsMove) builder.addMove(s.Target, s.Speed);
if (s.IsDwell) builder.addDwell(s.Milliseconds);
}
builder.endJob();
}
}
class OpcodeBuilder : JobBuilder
{
public void addMove(Point target, int speed) {
// emit controller opcodes
}
// beginJob, addDwell and endJob the same way
public byte[] getResult() { return new byte[0]; }
}
class TravelSheetBuilder : JobBuilder
{
public void addMove(Point target, int speed) {
// append one line an operator can read
}
// beginJob, addDwell and endJob the same way
public string getResult() { return ""; }
}
walk() is the only copy of the traversal, and it names
no output format. The two getResult() methods return different
types and so are absent from the interface, which is the pattern's one
permanent rough edge.Seen in the Wild
SAX parsing is the book's Builder almost exactly: the parser is the Director,
walking the document once, and a ContentHandler is the Builder,
turning that single stream of events into whatever representation you need. The
fluent variety is far more visible: Protocol Buffers generates a nested
Builder class for every message type, Java's
DateTimeFormatterBuilder assembles a formatter piece by piece, and
OkHttp's Request.Builder is the standard way to construct a request.
Java's StringBuilder is the one to be careful about; despite the
name it is a mutable buffer, not this pattern.
Comparisons
- Abstract Factory returns a finished object per call. A Builder is fed over many calls and answers at the end. Abstract Factory is about which family, Builder is about how it is assembled.
- Template Method is the usual shape of the
Director's
construct(): a fixed skeleton with the varying parts delegated. A Director delegates to another object, a template method delegates to a subclass. - Composite is very often what a Builder is building, since trees are exactly the sort of structure that is painful to assemble in one constructor call.
- Visitor is the same split viewed from the other side: Visitor walks a structure that already exists, a Director walks input in order to create one.
- Prototype is the cheaper answer when the variations you need are all small deltas on an existing configured object.
Why It Exists
My take
The Gang of Four version exists because traversal logic is expensive to duplicate and almost impossible to keep in sync. Anything that walks a structure in a particular order accumulates rules: what counts as a step, what order things happen in, what the edge cases are. Copy that walk to produce a second output format and you have not copied code, you have copied a specification, and only one of the copies will get the next amendment. The dwell bug above is what that looks like when it finally lands.
The fluent version exists for an entirely different reason: constructors are positional, unnamed, and all-or-nothing. A class with fifteen fields, ten of them optional, has no good constructor. You either write a telescoping series of overloads, or you make every field settable and give up immutability. The fluent builder is the workaround, and the reason it is everywhere is that most languages still cannot express "named optional arguments to an immutable object". Python and Kotlin can, and it is telling that the pattern is much rarer in both.
My position is that these should have been given different names thirty years ago and the confusion is now permanent. If you are arguing with someone about whether Builder is overused, establish which one you are each talking about first. Half of those arguments evaporate on the spot.
Criticisms
The field list gets written twice. Every field in the product appears again in the builder, plus a setter, and the two drift the moment someone adds a field to one and forgets the other. Some languages generate this and some do not; if yours does not, that duplication is a permanent tax and you should count it before starting.
It also moves validation from compile time to run time, and pretends this is
free. A constructor with four required parameters cannot be called wrong. A
builder with four required fields can be built after setting three, and the
compiler is perfectly happy; you find out at build(), or worse, you
find out later when something reads a field that was never set. There are staged
builder tricks that restore the compile-time check, and they cost one interface
per required field, which nobody does twice.
For the director-and-builder version specifically, the return type problem never really goes away. Because each builder produces a different type, the finished object cannot be collected through the shared interface without generics, casts, or a common supertype that means nothing. The book acknowledges this and moves on. In practice it is the reason the pattern is usually implemented once, admired, and then quietly replaced with three functions.