Builder
The Pizza constructor takes eight parameters, most optional.
Give the object a helper, the builder.
A constructor has many optional parameters
The picture from life
At a sandwich counter you say: wheat bread, add cheese, add lettuce, no onion, extra sauce, done. The sandwich is made in steps you choose, in any combination, and only when you say "done" do you get the finished sandwich.
The problem
What goes wrong when you do not use it.
The Pizza constructor takes eight parameters, most optional. Callers write Pizza("large", True, False, None, None, True, 2, "thin") and nobody remembers what each value means. Adding a ninth option breaks every call.
- creates
- A constructor has many optional parameters
- The same steps can make different looks of one thing
- The object must be valid only when fully built
The idea
How the pattern fixes it.
Give the object a helper, the builder. You call small, readable steps on it (withCheese(), withSize()) and finally build(). The builder checks everything and hands over the finished object.
- calls / uses
- creates
Who is who
- Pizza
- The finished product.
- PizzaBuilder
- Collects choices through chainable methods. build() returns a Pizza.
- Director (optional)
- A recipe that calls builder steps in a fixed order, e.g. margherita().
Build it in steps
- 1Create a builder class holding temporary fields.
- 2Add one method per option; return the builder itself.
- 3Add build() that validates and returns the product.
- 4Optionally add recipes for common combinations.
Watch it run
Step through the calls. Tap an arrow to jump to it.
Each step has a clear name. It returns the builder, so calls chain.
size('large')The code
Python and Java. Try writing the classes yourself from the diagram first.
class Pizza:
def __init__(self, size, cheese, toppings):
self.size = size
self.cheese = cheese
self.toppings = toppings
def describe(self):
extras = ", ".join(self.toppings) or "plain"
return f"{self.size} pizza, cheese={self.cheese}, {extras}"
class PizzaBuilder:
def __init__(self):
self._size = "medium"
self._cheese = False
self._toppings = []
def size(self, size):
self._size = size
return self # return the builder so calls chain
def cheese(self):
self._cheese = True
return self
def topping(self, name):
self._toppings.append(name)
return self
def build(self):
return Pizza(self._size, self._cheese, list(self._toppings))
def demo():
return PizzaBuilder().size("large").cheese().topping("olives").topping("corn").build().describe()What you gain
- Readable creation: each step has a name
- Optional parts without constructor explosion
- The object can be immutable once built
What it costs
- A whole extra class per product
- Overkill for objects with two or three fields
You have already met it in
- StringBuilder in Java
- Lombok's @Builder and Protocol Buffers builders
- Query builders such as Django's QuerySet chaining
Often mixed up with
- Factory MethodFactory Method chooses which class; Builder shapes one complex object over several steps.
- PrototypePrototype copies an existing object; Builder makes a new one from scratch.
Make it stick
Create an HttpRequestBuilder with method, url, header(), body() and timeout(). Make build() refuse a request with no url.