design patterns · medium

GoF Builder — Step-by-Step Complex Object Construction

The Builder pattern separates complex object construction into discrete named steps. A PizzaBuilder interface exposes setSize(), setCrust(), setSauce(), addTopping(), stuffCrust(), build(). ItalianPizzaBuilder accumulates state in a pizza_ member, returning *this for method chaining. A Director encapsulates the recipe (buildMargherita calls steps in order). The client only calls director.buildMargherita(builder) — oblivious to steps or the concrete builder type. Compared to telescoping constructors: readable (named setters), safe (no positional boolean confusion), extensible (new builder for vegan/gluten-free with zero Director changes). build() uses std::move to transfer ownership. Same Director + different ConcreteBuilder → different product variant.

🔑 Key line

Builder: Director calls named setters on a Builder interface one step at a time; ConcreteBuilder accumulates state; build() returns the completed product — replaces telescoping constructors; client is decoupled from both construction steps and product assembly.

The code

// Product — complex object under construction
class Pizza {
string size_, crust_, sauce_;
vector<string> toppings_;
bool stuffed_crust_ = false;
};
// Builder interface — step-by-step construction
class PizzaBuilder {
public:
virtual PizzaBuilder& setSize(string s) = 0;
virtual PizzaBuilder& setCrust(string c) = 0;
virtual PizzaBuilder& setSauce(string s) = 0;
virtual PizzaBuilder& addTopping(string t) = 0;
virtual PizzaBuilder& stuffCrust(bool b) = 0;
virtual Pizza build() = 0;
};
// ConcreteBuilder
class ItalianPizzaBuilder : public PizzaBuilder {
Pizza pizza_;
public:
PizzaBuilder& setSize(string s) override {
pizza_.size_ = s;
return *this;
}
PizzaBuilder& setCrust(string c) override {
pizza_.crust_ = c;
return *this;
}
PizzaBuilder& setSauce(string s) override {
pizza_.sauce_ = s;
return *this;
}
PizzaBuilder& addTopping(string t) override {
pizza_.toppings_.push_back(t);
return *this;
}
PizzaBuilder& stuffCrust(bool b) override {
pizza_.stuffed_crust_ = b;
return *this;
}
Pizza build() override {
return move(pizza_);
}
};
// Director — knows the recipe; client is decoupled from steps
class Director {
public:
Pizza buildMargherita(PizzaBuilder& b) {
return b.setSize("medium")
.setCrust("thin")
.setSauce("tomato")
.addTopping("mozzarella")
.addTopping("basil")
.stuffCrust(false)
.build();
}
};
// Client
ItalianPizzaBuilder b;
Director d;
Pizza p = d.buildMargherita(b);

What this lesson walks through

  1. 01Telescoping constructor — the anti-pattern Builder solves
  2. 02Director issues setSize — first field set
  3. 03setCrust + setSauce — fields accumulate
  4. 04addTopping × 2 — variadic field via repeated calls
  5. 05stuffCrust + build() — product returned
  6. 06Director + multiple builders — same recipe, different products

Pizza(size, crust, cheese, sauce, toppings, stuffed_crust) — 6 parameters, all positional. Callers can't tell bool from bool; optional fields have ugly overloads. This is the telescoping constructor anti-pattern. Builder replaces all of this with named, step-by-step, readable construction.

See it animated — step by step, at your own pace

Unlock the full interactive walkthrough of GoF Builder — Step-by-Step Complex Object Construction and 100+ animated C++ interview lessons.

← Previous
GoF Factory Method — Deferred Object Creation via Subclasses
Next →
Abstract Factory Pattern