Skip to main content

Builder

complexitypopularity

Assemble a complex object step by step, so the same construction sequence can produce different representations without a monstrous constructor.

The problem

Some objects need laborious, step-by-step initialization across many fields and nested objects. A simple House needs four walls, a floor, a door, a couple of windows and a roof. Then someone asks for a backyard, heating, plumbing and wiring.

The obvious fixes both fail. Subclass House for every combination and the hierarchy metastasizes: add a porch style and it doubles. Write one giant constructor instead and you get calls where most parameters are unused, because only a fraction of houses have swimming pools.

The solution

Take the construction code out of the product class entirely and move it into a separate object: the builder. Construction becomes a set of steps - buildWalls, buildRoof, buildPool - and you invoke only the ones this particular object needs.

Different representations need different implementations of the same steps: cabin walls are timber, castle walls are stone. So write several concrete builders implementing the same step interface, and the same sequence of calls yields a cabin from one and a villa from another.

Optionally, extract that sequence into a director. The director decides the order of steps; the builder decides how each step is done. It is worth having when you replay the same recipes across the program, and it is entirely skippable otherwise. Note where the finished product comes from: the builder, not the director, because the director must stay ignorant of concrete product types.

ApplicationDirector (optional)StoneHouseBuilderHouse (product)new StoneHouseBuilder()1buildVilla(builder)2reset()3buildWalls("stone")4buildRoof("gable") + buildPool()5getResult()6returns House7
  1. 1The client picks a representation by picking a builder. Stone today, timber tomorrow, same recipe either way.
  2. 2The client hands the builder to the director and names a configuration. Skipping this and calling the steps yourself is entirely legal.
  3. 3Every recipe starts with a blank product, so a reused builder cannot leak last week's garage into this house.
  4. 4The director knows the order of steps; the builder knows how each step is done. Neither knows the other's half.
  5. 5A villa gets a pool. A cabin recipe would simply not call this step, no null arguments required.
  6. 6The result comes from the builder, not the director, because the director must not know what type it just produced.
  7. 7The client receives a complete house. It was never handed a partially built one, which is the quiet safety benefit.

Structure

Builder interface, concrete builders, products, and an optional director. The products are the odd ones out here: unlike every other creational pattern, they need not share a class hierarchy or interface at all.

«interface»HouseBuilderreset()buildWalls(material)buildRoof(style)buildGarage()buildPool()BUILDERStoneHouseBuilderreset()buildWalls(material)...getResult(): HouseCONCRETEBlueprintBuilderreset()buildWalls(material)...getResult(): BlueprintCONCRETEHousewallsroofgaragepoolPRODUCTBlueprintsections: listPRODUCTDirectorbuildCabin(b)buildVilla(b)DIRECTOR
implementsusescreates

Code

Same example three ways: one construction sequence that produces either a house or the paperwork describing it.

// Option A: the telescoping constructor.
class House is
constructor House(walls, roof)
constructor House(walls, roof, garage)
constructor House(walls, roof, garage, pool)
constructor House(walls, roof, garage, pool, heating, wiring, garden)
 
// The call site, in all its glory:
house = new House("stone", "gable", null, null, true, true, null)
// Quick, what is the fourth argument? Exactly.
 
// Option B: a subclass per configuration.
class HouseWithGarage extends House is ...
class HouseWithPool extends House is ...
class HouseWithGarageAndPool extends House is ...
// Add "porch style" and the hierarchy doubles again.
// Call only the steps this house actually needs.
builder = new StoneHouseBuilder()
builder.buildWalls("stone")
builder.buildRoof("gable")
builder.buildPool()
House house = builder.getResult()
 
// Or name the recipe once and replay it, on any builder:
director.buildVilla(new StoneHouseBuilder()) // a stone villa
director.buildVilla(new BlueprintBuilder()) // its paperwork
 
// No null arguments, no HouseWithGarageAndPoolAndPorch subclass,
// and nobody ever sees a half-finished house.
// The products. They are complex, and they are not related to
// each other by any interface. Builder is fine with that.
class House is
// Walls, a roof, and optionally a garage, pool, heating, wiring.
 
class Blueprint is
// A written description of whatever the same steps would build.
 
// The builder interface names every possible construction step.
interface HouseBuilder is
method reset()
method buildWalls(material)
method buildRoof(style)
method buildGarage()
method buildPool()
 
// A concrete builder implements the steps for one representation.
class StoneHouseBuilder implements HouseBuilder is
private field house: House
 
constructor StoneHouseBuilder() is
this.reset()
 
// Start from a blank product so a reused builder stays honest.
method reset() is
this.house = new House()
 
method buildWalls(material) is
// Lay stone walls and record them on the house.
 
method buildRoof(style) is
// Frame and tile a roof of the requested style.
 
method buildGarage() is
// Pour a slab and hang a door.
 
method buildPool() is
// Dig, line, fill. Regret later.
 
// Fetching the result cannot live on the interface: different
// builders return unrelated types. Resetting here leaves the
// builder ready for the next job.
method getResult(): House is
product = this.house
this.reset()
return product
 
// Same steps, completely different product.
class BlueprintBuilder implements HouseBuilder is
private field doc: Blueprint
 
constructor BlueprintBuilder() is
this.reset()
 
method reset() is
this.doc = new Blueprint()
 
method buildWalls(material) is
// Write down the wall spec instead of laying anything.
 
method buildRoof(style) is
// Document the roof geometry.
 
method buildGarage() is
// Add a garage section to the document.
 
method buildPool() is
// Add pool dimensions and a safety notice.
 
method getResult(): Blueprint is
// Return the document and reset.
 
// The director stores reusable step sequences. Strictly optional:
// the client is allowed to drive the builder directly.
class Director is
method buildCabin(builder: HouseBuilder) is
builder.reset()
builder.buildWalls("timber")
builder.buildRoof("shed")
// No garage, no pool. Steps you skip cost you nothing.
 
method buildVilla(builder: HouseBuilder) is
builder.reset()
builder.buildWalls("stone")
builder.buildRoof("gable")
builder.buildGarage()
builder.buildPool()
 
// The client wires a builder to a director and collects the result
// from the builder, because the director does not know product types.
class Application is
method makeHouse() is
director = new Director()
 
StoneHouseBuilder builder = new StoneHouseBuilder()
director.buildVilla(builder)
House house = builder.getResult()
 
BlueprintBuilder docBuilder = new BlueprintBuilder()
director.buildVilla(docBuilder)
Blueprint doc = docBuilder.getResult()

When to use it

  • You are staring down a telescoping constructor: ten optional parameters and a fan of overloads that all delegate to the worst one.
  • You need several representations of a product built from similar steps that differ only in the details, such as stone versus wooden houses.
  • You are constructing Composite trees or other recursive structures. Builder steps can call themselves, and you can defer a step without breaking the final product.

Pitfalls

  • More classes, always. A builder interface plus one class per representation plus a director is real overhead. For a three-field object it is theatre.
  • Forgetting to reset. Reuse a builder without clearing the product and last job's pool shows up in this job's cabin. Reset in the constructor and again after handing off.
  • Directors that know too much. The moment a director references a concrete product type to return it, you have coupled it to the thing it was designed to be ignorant of.

Don't confuse it with

  • Factory Method. A factory method is one call in, one product out. Builder is a conversation. Many designs start at Factory Method and migrate here when construction outgrows a single call.
  • Abstract Factory. Families of related products, returned immediately, versus one complex product assembled over time. Abstract Factory also requires its products to share interfaces, which Builder pointedly does not.
  • Fluent setters. Chained withX() calls are a common Builder flavor, but chaining is a syntax choice. The pattern is the separated construction object, not the dots.

Check yourself

Question 1 of 5

Which two symptoms does Builder exist to cure?