Builder design pattern helps us by providing a system to construct complex objects, step by step. Especially when an object has multiple varying parts, and we need an easy way to set up those parts.
Where other patterns like the Factory pattern, construct an object in a single step, the Builder pattern enables multi-step build. Builder pattern encapsulates the complexity of the complex multi-step build.
NOTES
In this article, we discuss the implementation of the Builder Pattern in Python.
See the Builder in other languages in the “Other Code Implementations” section. Or, use the link below to check the details of the Builder Pattern-
Implementation
Complexity of the builder impleentation depends on the item object. If the item object is complex then it can be very complex to implement the Builder pattern.
Here is how a basic Builder pattern implementation should look-
NOTES
You dont need to return the builder object, at the end of the every setter, if you don’t want to use method chaining.
Here is a simple Builder pattern implementation in Python-
# Item class
class Item:
def __init__(self) -> None:
self.prop1: int | None = None
self.prop2: int | None = None
# Here we can have other utility
# methods for Item
def __str__(self) -> str:
return f"Prop1: {self.prop1}\nProp2: {self.prop2}"
# Item builder
class ItemBuilder:
def __init__(self) -> None:
self.item = Item()
def set_prop1(self, prop1: int) -> "ItemBuilder":
self.item.prop1 = prop1
return self # return current object for method chaining
def set_prop2(self, prop2: int) -> "ItemBuilder":
self.item.prop2 = prop2
return self # return current object for method chaining
# Final method to be called
# in the build process
def build(self) -> Item:
return self.item
# Demo usage
def main() -> None:
builder = ItemBuilder()
item = builder.set_prop1(100).set_prop2(999).build()
print(item)
if __name__ == "__main__":
main()
PythonOutput:
Prop1: 100
Prop2: 999
PlaintextExamples
Here we have discussed 2 examples-
Example #1: Request Builder
Let’s create a request builder, where we can set the HTTP host, headers, body elements step by step. This will allow use to build complex request object easily.
Request Types
NOTES
This is just to define the types of reqeust. This enum is not directly related to the Builder pattern implementation.
from enum import Enum
# Request types
class RequestType(Enum):
GET = "GET"
POST = "POST"
PUT = "PUT"
PATCH = "PATCH"
DELETE = "DELETE"
PythonRequest Class [Item class]
# Reqeust class
class Request:
def __init__(
self,
) -> None:
self.url: str | None = None
self.request_type: RequestType | None = None
self.header: Dict[str, str] = {}
self.body: Dict[str, str] = {}
# Utility function for the class
# Not directly related to builder pattern
def validate_request(self) -> None:
if self.url is None:
raise ValueError("URL is missing")
if self.request_type is None:
raise ValueError("Request type(reqeust_type) is missing")
def send(self) -> None:
"""Dummy implemnetation for request sending"""
self.validate_request()
print("Sending reqeust...")
print(f"URL: {self.url}")
print("Headers: ", self.header)
print("Body: ", self.body)
# Write the reqeust sending implementation here
PythonRequest Builder Class [Builder Class]
# Request builder
class RequestBuilder:
def __init__(self):
self.request = Request()
def set_url(self, url: str) -> "RequestBuilder":
self.request.url = url
return self
def set_type(self, request_type: RequestType) -> "RequestBuilder":
self.request.request_type = request_type
return self
def add_header(self, key: str, value: str) -> "RequestBuilder":
self.request.header[key] = value
return self
def add_body(self, key: str, value: str) -> "RequestBuilder":
self.request.body[key] = value
return self
def build(self) -> Request:
return self.request
Demo
# Usage demo
def main() -> None:
request_builder = RequestBuilder()
request = (
request_builder.set_url("https://bigboxcode.com/request-test")
.set_type(RequestType.POST)
.add_header("Content-Type", "application/json")
.add_header("X-AUTH-TOKEN", "someTokenHere")
.add_header("X-SOME-HEADER", "someRandomHeaderValueHere")
.add_body("unit_id", "99")
.add_body("code", "88C3ABK")
.build()
)
request.send()
if __name__ == "__main__":
main()
PHPOutput
Sending reqeust...
URL: https://bigboxcode.com/request-test
Headers: {'Content-Type': 'application/json', 'X-AUTH-TOKEN': 'someTokenHere', 'X-SOME-HEADER': 'someRandomHeaderValueHere'}
Body: {'unit_id': '99', 'code': '88C3ABK'}
PlaintextExample #2: Vehicle Builder
In this example we are creating builders for building vehicle objects, step-by-step. In this example we are using a an abstract builder to define a common interface for the builders.
Car Class [Item Class]
# Car item class
class Car:
def __init__(self):
self._wheel = 0
self._engine = 0
self._seat = 0
self._door = 0
self._interior = False
@property
def wheel(self) -> int:
return self._wheel
@property
def engine(self) -> int:
return self._engine
@property
def seat(self) -> int:
return self._seat
@property
def door(self) -> int:
return self._door
@property
def interior(self) -> bool:
return self._interior
def __str__(self) -> str:
return (
f"Car: Wheel -> {self._wheel} | Engine -> {self._engine} | "
f"Seat -> {self._seat} | Door -> {self._door} | Interior -> {str(self._interior).lower()}"
)
PythonPlane Class [Item Class]
# Plane item class
class Plane:
def __init__(self):
self._wheel = 0
self._engine = 0
self._seat = 0
self._door = 0
self._wing = 0
self._interior = False
@property
def wheel(self) -> int:
return self._wheel
@property
def engine(self) -> int:
return self._engine
@property
def seat(self) -> int:
return self._seat
@property
def door(self) -> int:
return self._door
@property
def wing(self) -> int:
return self._wing
@property
def interior(self) -> bool:
return self._interior
def __str__(self) -> str:
return (
f"Plane: Wheel -> {self._wheel} | Engine -> {self._engine} | "
f"Seat -> {self._seat} | Door -> {self._door} | Wing -> {self._wing} | "
f"Interior -> {str(self._interior).lower()}"
)
PythonVehicle Builder Abstract Class [Abstract Builder]
from abc import ABC, abstractmethod
# Abstract vehichle builder
class VehicleBuilder(ABC):
@abstractmethod
def add_wheel(self, no_of_wheel: int) -> None:
pass
@abstractmethod
def add_engine(self, no_of_engine: int) -> None:
pass
@abstractmethod
def add_seat(self, no_of_seat: int) -> None:
pass
@abstractmethod
def add_interior(self) -> None:
pass
@abstractmethod
def add_door(self, no_of_door: int) -> None:
pass
@abstractmethod
def add_wing(self, no_of_wing: int) -> None:
pass
PythonCar Builder Class [Concrete Builder]
# Car builder
class CarBuilder(VehicleBuilder):
def __init__(self):
self.vehicle = Car() # Create a Car object
def add_wheel(self, no_of_wheel: int) -> None:
print(f"Add {no_of_wheel} wheels")
self.vehicle._wheel += no_of_wheel # Update the vehicle object
def add_engine(self, no_of_engine: int) -> None:
print(f"Add {no_of_engine} engine")
self.vehicle._engine += no_of_engine # Update the vehicle object
def add_seat(self, no_of_seat: int) -> None:
print(f"Add {no_of_seat} Seat")
self.vehicle._seat = no_of_seat # Update the vehicle object
def add_interior(self) -> None:
print("Add interior")
self.vehicle._interior = True # Update the vehicle object
def add_door(self, no_of_door: int) -> None:
print(f"Add {no_of_door} door")
self.vehicle._door += no_of_door # Update the vehicle object
def add_wing(self, no_of_wing: int) -> None:
raise Exception("Cannot add wings")
def build(self) -> Car:
return self.vehicle # Return the built Car object
PythonPlane Builder Class [Concrete Builder]
# Plane builder
class PlaneBuilder(VehicleBuilder):
def __init__(self):
self.vehicle = Plane() # Create a Plane object
def add_wheel(self, no_of_wheel: int) -> None:
print(f"Add {no_of_wheel} wheels")
self.vehicle._wheel += no_of_wheel # Update the vehicle object
def add_engine(self, no_of_engine: int) -> None:
print(f"Add {no_of_engine} engine")
self.vehicle._engine += no_of_engine # Update the vehicle object
def add_seat(self, no_of_seat: int) -> None:
print(f"Add {no_of_seat} Seat")
self.vehicle._seat = no_of_seat # Update the vehicle object
def add_interior(self) -> None:
print("Add interior")
self.vehicle._interior = True # Update the vehicle object
def add_door(self, no_of_door: int) -> None:
print(f"Add {no_of_door} door")
self.vehicle._door += no_of_door # Update the vehicle object
def add_wing(self, no_of_wing: int) -> None:
print(f"Add {no_of_wing} wing")
self.vehicle._wing += no_of_wing # Update the vehicle object
def build(self) -> Plane:
return self.vehicle # Return the built Plane object
PythonVehicle Producer Class [Producer]
NOTES
If we want, we can avoid creating this producer class, and move all the code in it in the client(where we are using the implementation) directly.
# Producer
class VehicleProducer:
def build_car(self, car_builder: CarBuilder) -> CarBuilder:
car_builder.add_wheel(4)
car_builder.add_engine(1)
car_builder.add_door(4)
car_builder.add_seat(4)
car_builder.add_interior()
return car_builder
def build_plane(self, plane_builder: PlaneBuilder) -> PlaneBuilder:
plane_builder.add_wheel(3)
plane_builder.add_engine(2)
plane_builder.add_door(4)
plane_builder.add_seat(120)
plane_builder.add_interior()
try:
plane_builder.add_wing(2)
except Exception as e:
raise RuntimeError(e)
return plane_builder
PythonDemo
# Demo usage
def main():
vehicle_producer = VehicleProducer()
print("Building Car:")
car_builder = CarBuilder()
vehicle_producer.build_car(car_builder)
car = car_builder.build()
print(f"\nFinal result:\n{car}")
print("\nBuilding Plane:")
plane_builder = PlaneBuilder()
vehicle_producer.build_plane(plane_builder)
plane = plane_builder.build()
print(f"\nFinal result:\n{plane}")
if __name__ == "__main__":
main()
PythonOutput:
Building Car:
Add 4 wheels
Add 1 engine
Add 4 door
Add 4 Seat
Add interior
Final result:
Car: Wheel -> 4 | Engine -> 1 | Seat -> 4 | Door -> 4 | Interior -> true
Building Plane:
Add 3 wheels
Add 2 engine
Add 4 door
Add 120 Seat
Add interior
Add 2 wing
Final result:
Plane: Wheel -> 3 | Engine -> 2 | Seat -> 120 | Door -> 4 | Wing -> 2 | Interior -> true
PlaintextSource Code
Use the following link to get the source code:
Other Code Implementations
Use the following links to check Builder Pattern implementation in other programming languages.