Design Pattern: Builder Pattern in Python

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-

We have an “Item” class. This represents our main object. This item class can be pre-existing in the system.
Create a builder class. Here we have crated a class named “ItemBuilder“.
In the builder create an object of the item class, and store it in an attrubuite.
Define methods to set different attributes of the item object.
Return the builder object “self” from each setter method, to make use of method chaining.
Finally create a “build” method and return the item object form it.

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()
Python

Output:

Prop1: 100
Prop2: 999
Plaintext

Examples

Here we have discussed 2 examples-

Request Builder – builder for http request sender.
Vehicle Builder – builder for vehicle object like car, bus, plane, etc.

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.

Define an enum by creating class named “RequestType” and inherit from “Enum“.
Define items for different types of reqeusts- GET, POST, etc.
from enum import Enum


# Request types
class RequestType(Enum):
    GET = "GET"
    POST = "POST"
    PUT = "PUT"
    PATCH = "PATCH"
    DELETE = "DELETE"
Python

Request Class [Item class]

Create class “Reqeust“.
Initialize attributes – url, request_type, header, body, in the “__init__” method.
Define method for sending the reqeust.
# 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
Python

Request Builder Class [Builder Class]

Create class named “ReqeustBuilder“.
In the “__init__” method create an object of “Reqeust” class.
Define method for setting values of the reqeust object.
Define method “build” and in the method return the reqest object.
# 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

We can create a “RequestBuilder” object
Build the reqeust step by step. This will give us a “Request” class object.
Finally call the “send” method to send the requst.
# 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()
PHP

Output

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'}
Plaintext

Example #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]

Create class “Car“.
Define an initialize attributes- _wheel, _engine, _seat, _door, _interior.
Define the getters with “@property” decorator.
# 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()}"
        )
Python

Plane Class [Item Class]

Create class “Plane“.
Define and initialize attributes in “__init__“.
# 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()}"
        )
Python

Vehicle Builder Abstract Class [Abstract Builder]

Define abstract class “VehicleBuilder“.
Declare abstract methods for adding elements to a vehicle.
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
Python

Car Builder Class [Concrete Builder]

Create class “CarBuilder“.
Inherit from “VehicleBuilder“.
Create a “Car” class object in the “__init__” method.
Define methods for setting values for the “car” object.
Define a method “build” and return the “car” object.
# 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
Python

Plane Builder Class [Concrete Builder]

Create class “PlaneBuilder“.
Create a “Plane” class object in the “__init__” method.
Define setters, and finally define a “build” method to return the “plane” object.
# 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
Python

Vehicle 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.

Create class “VehicleProducer“.
Define method “build_car“, accept a “CarBuilder” object and construct a builder in the method.
Do the same thing for plane, by defining method “build_plane“.
# 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
Python

Demo

Create a producer object.
Call the “build_car” method and pass a “CarBuilder” object.
Do the same for plane.
Finally call the “build” method, to finish the construction of the object.
# 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()
Python

Output:

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
Plaintext

Source 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.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.