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.
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 constructionclass Pizza { string size_, crust_, sauce_; vector<string> toppings_; bool stuffed_crust_ = false;};
// Builder interface — step-by-step constructionclass 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;};
// ConcreteBuilderclass 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 stepsclass Director {public: Pizza buildMargherita(PizzaBuilder& b) { return b.setSize("medium") .setCrust("thin") .setSauce("tomato") .addTopping("mozzarella") .addTopping("basil") .stuffCrust(false) .build(); }};
// ClientItalianPizzaBuilder b;Director d;Pizza p = d.buildMargherita(b);What this lesson walks through
- 01Telescoping constructor — the anti-pattern Builder solves
- 02Director issues setSize — first field set
- 03setCrust + setSauce — fields accumulate
- 04addTopping × 2 — variadic field via repeated calls
- 05stuffCrust + build() — product returned
- 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.