🏗️ Builder Pattern Tutorial
The Builder Pattern is a creational design pattern that lets you construct complex objects step by step, decoupling the construction process from the final representation.
🔧 When to Use It ?
- Object creation involves many optional parts or configurations.
- You want to reuse the same construction process to create different representations.
🧩 Scenario
Imagine ordering a custom meal combo at a fast-food restaurant:
You can choose a burger, drink, and side.
A server (Builder) helps you assemble your meal (Product) step by step.
Different meal combos (Representations) are possible from the same process.
🧱 Components of the Builder Pattern
Product: The complex object being built.
Builder: Specifies an abstract interface for building the parts.
ConcreteBuilder: Implements the Builder interface.
Director (optional): Constructs an object using the Builder.
classDiagram
direction LR
class Product {
+addPart(part)
+__str__()
}
class Builder {
<<interface>>
+addPartA()
+addPartB()
+getResult()
}
class ConcreteBuilderA {
+addPartA()
+addPartB()
+getResult()
}
class Director {
-builder: Builder
+construct()
}
Product <.. ConcreteBuilderA : builds
Builder <|-- ConcreteBuilderA
Director --> Builder : uses
Lets translate our food order analogy to build different types of burger eg. Vegetarina and Meat burger
Burger.py (Product)
Our Burger is a product which is created using different ingredient eg. Patty (Veg/Meat), toppings, sauce etc.
class Burger():
def __init__(self):
self.ingredients = []
def add(self, ingredient):
self.ingredients.append(ingredient)
def result(self):
return "Burge with :" + ",".join(self.ingredients)
BurgerBuilder.py (Builder Interface)
Our Burger Builder provide interface to build Burger Product.
def BurgerBuilder(animal_type: str):
def __init__(self, burger):
self.burger = burger
def add_bun(self, bun):
pass
def add_patty(self, patty):
pass
def add_topping(self, topping):
pass
def add_sauce(self, sauce):
pass
def get_burger(self):
return self.burger
Implement Concrete Builders
Now we implement concrete burger types:
class VegBurgerBuilder(BurgerBuilder):
def __init__(self):
self.burger = Burger()
def add_bun(self):
self.burger.add("Whole Wheat Bun")
def add_patty(self):
self.burger.add("Veg Patty")
def add_sauce(self):
self.burger.add("Tomato Ketchup")
def get_burger(self):
return self.burger
class ChickenBurgerBuilder(BurgerBuilder):
def __init__(self):
self.burger = Burger()
def add_bun(self):
self.burger.add("Sesame Bun")
def add_patty(self):
self.burger.add("Chicken Patty")
def add_sauce(self):
self.burger.add("Mayonnaise")
def get_burger(self):
return self.burger
Implement Director
Finally we implement a director which will cook the burger to a given type
class Cook:
def __init__(self, builder : BurgerBuilder):
self.builder = builder
def make_burger(self):
self.builder.add_bun()
self.builder.add_patty()
self.builder.add_sauce()
return self.builder.get_burger()
Client Code
burgerBuilder = VegBurgerBuilder()
cook = Cook(burgerBuilder)
burger = cook.make_burger()
print(burger.result())
# Output: Burger with Whole Wheat Bun, Veg Patty, Tomato Ketchup
Benefits of Using Builder Pattern
-
Separates construction logic from the representation.
-
Supports incremental construction and different representations.
-
Promotes immutability and code reusability.
The Builder Pattern here supports multiple key Object-Oriented Programming (OOP) principles. Here’s how:
🔑 1. Single Responsibility Principle (SRP)
“A class should have only one reason to change.”
-
Product class (e.g., Order) holds data and handles representation.
-
Builder class handles construction logic step-by-step.
-
Director class coordinates the construction process.
✅ Separation of concerns makes each class easier to maintain and test.
🔑 2. Encapsulation
- Construction logic is encapsulated within the Builder.
- The Client doesn’t need to know how the product is assembled—only how to request it.
🔑 3. Abstraction
- Defines an abstract Builder interface to build parts of the product.
- Multiple Concrete Builders can implement this interface differently.
✅ Allows flexibility in constructing different representations of the same object.
🔑 4. Open/Closed Principle (OCP)
“Software entities should be open for extension, but closed for modification.”
✅ Makes the system extensible and robust to change.
🔑 Bonus: Polymorphism
- Builders can be swapped dynamically thanks to interface-based programming.
- The Director works with any class that implements the OrderBuilder interface.
✅ Enhances flexibility and decoupling.