TUTORIAL SERIES

Design Patterns in Python: Builder

Crafting with Precision

Amir Lavasani
7 min readDec 3, 2023

Have you encountered recurring coding challenges? Imagine having a toolbox of tried-and-true solutions readily available. That’s precisely what design patterns provide. In this series, we’ll explore what these patterns are and how they can elevate your coding skills.

Understanding the Builder Pattern

What is the Builder Design Pattern?

The Builder Design Pattern is a creational design pattern that focuses on constructing complex objects step by step.

It separates the construction of an object from its representation, allowing the same construction process to create different representations.

When to Use the Builder Pattern:

The Builder Pattern is most useful in the following situations:

  1. Complex Object Construction: When an object needs numerous optional components or configurations, and a cluttered constructor isn’t practical.
  2. Multiple Representations: When you want to create various object representations using the same construction process.

Practical Example: Form Generation

The Builder Design Pattern finds practical use in creating dynamic forms in software development. we focus on using this pattern for form generation in Python.

Complex form structures, customization, validation rules, and styling can be systematically implemented using the Builder Pattern, simplifying the construction of intricate forms.

Dall-E generated image with the following concept: Lego bricks to form a more intricate structure, highlighting the modularity of the Builder Pattern.

Terminology and Key Components

To understand the Builder Pattern, let’s break down its vital components:

  1. Director: Guides complex object creation, ensuring cooperation among builders.
  2. Builder Interface: Blueprint for complex object construction, enforcing method consistency.
  3. Concrete Builder: Workers specializing in specific complex object parts.
  4. Product: End result, varies based on the concrete builder used.
Builder design pattern structure diagram. Image from refactoring.guru

Builder Pattern Implementation in Python

Let’s dive into implementing the Builder Pattern in Python for creating complex objects. We’ll explore the key components and practical steps.

Step 1: Director Class

The Director class oversees the construction process by working with a builder. The Director ensures the correct order of steps in creating a complete complex object.

class Director:
def construct(self, builder):
builder.build_part_a()
builder.build_part_b()
builder.build_part_c()

Step 2: Builder Interface

The Builder Interface is vital for outlining the methods that concrete builder classes must implement. It serves as the blueprint for constructing objects.

from abc import ABC, abstractmethod

class Builder(ABC):
@abstractmethod
def build_part_a(self):
pass

@abstractmethod
def build_part_b(self):
pass

@abstractmethod
def build_part_c(self):
pass

This interface enforces the implementation of three methods: build_part_a(), build_part_b(), and build_part_c().

Step 3: Concrete Builder Classes

Each concrete builder class creates a specific variation of the complex object. We may have multiple concrete builders for different object types.

class ConcreteBuilderA(Builder):
def __init__(self):
self.product = Product()

def build_part_a(self):
self.product.add("Part A1")

def build_part_b(self):
self.product.add("Part B1")

def build_part_c(self):
self.product.add("Part C1")
class ConcreteBuilderB(Builder):
def __init__(self):
self.product = Product()

def build_part_a(self):
self.product.add("Part A2")

def build_part_b(self):
self.product add("Part B2")

def build_part_c(self):
self.product.add("Part C2")

Building Complex Objects Step by Step

Now, let’s bring it all together. We create a client class and a product class that uses the Director and Builder to construct complex objects.

class Product:
def __init__(self):
self.parts = []

def add(self, part):
self.parts.append(part)
class Client:
def construct_product(self, builder):
director = Director()
director.construct(builder)
return builder.product
# Main section to build products with different builders
if __name__ == "__main__":
client = Client()

builder_a = ConcreteBuilderA()
product_a = client.construct_product(builder_a)
print("Product built by ConcreteBuilderA:")
for part in product_a.parts:
print(part)

builder_b = ConcreteBuilderB()
product_b = client.construct_product(builder_b)
print("Product built by ConcreteBuilderB:")
for part in product_b.parts:
print(part)

Form Generator using Builder Pattern

In this implementation, we are using the Builder Pattern to create a form generator.

  1. Product: Define the product, which represents a form field with a label and input type.
  2. Builder (Abstract Builder): Create an abstract builder interface with methods for adding form fields and building the form.
  3. Concrete Builder: Implement a concrete builder that adds specific form fields and builds the form.
  4. Director: Implement a director responsible for orchestrating the construction of the form using the builder.
from abc import ABC, abstractmethod

# Step 1: Define the Product
class FormField:
"""Represents a form field with a label and input type."""

def __init__(self, label, input_type):
self.label = label
self.input_type = input_type

def __str__(self):
return f"{self.label}: <input type='{self.input_type}'>"

# Step 2: Define the Builder (Abstract Builder)
class Builder(ABC):
"""Abstract Builder Interface for constructing forms."""

@abstractmethod
def add_name_field(self):
pass

@abstractmethod
def add_email_field(self):
pass

@abstractmethod
def add_subscribe_field(self):
pass

@abstractmethod
def build(self):
pass

# Step 3: Implement the Concrete Builder
class FormBuilder(Builder):
"""Concrete Builder for constructing forms."""

def __init__(self):
self.form = []

def add_name_field(self):
"""Add a name field to the form."""
self.form.append(FormField("Name", "text"))
return self

def add_email_field(self):
"""Add an email field to the form."""
self.form.append(FormField("Email", "email"))
return self

def add_subscribe_field(self):
"""Add a subscribe checkbox to the form."""
self.form.append(FormField("Subscribe", "checkbox"))
return self

def build(self):
"""Build the form.

Returns:
str: The HTML representation of the form.
"""
form = "\n".join(str(field) for field in self.form)
return f"<form>\n{form}\n</form>"

# Step 4: Implement the Director
class FormBuilderDirector:
"""Director for constructing complex forms using a specific builder."""

def __init__(self, builder):
self.builder = builder

def construct_form(self):
"""Construct the form using the builder."""
self.builder.add_name_field().add_email_field().add_subscribe_field()

# Step 5: Implement the Client
if __name__ == "__main__":
# Demonstrate form generation using the Builder Pattern
builder = FormBuilder()
director = FormBuilderDirector(builder)
director.construct_form()
form = builder.build()

print("Generated Form:")
print(form)

GitHub Repo 🎉

Explore all code examples and design pattern implementations on GitHub!

Comparing Builder Pattern and Direct Object Creation

Here are the contrasting aspects between the Builder Pattern and Direct Object Creation.

Builder Pattern:

  1. Structured Construction: Provides an organized way to build complex objects, separating construction from representation for better code maintenance.
  2. Flexibility: Allows the creation of complex objects with various configurations using different builders.
  3. Reusability: Builder classes are reusable, facilitating the creation of multiple object instances.
  4. Readability: Offers clear and comprehensible construction processes for easier code understanding.

Direct Object Creation:

  1. Simplicity: Suitable for straightforward objects or when different configurations are not needed.
  2. Less Overhead: For small, uncomplicated objects, it avoids unnecessary complexity introduced by the Builder Pattern.
Dall-E generated image with the following concept: A series of concentric circles, each representing a different societal level

Using Builder in Your Projects

Incorporating the Builder Pattern into your software projects involves the following key steps and best practices:

Integration and Best Practices:

  1. Identify Complex Object Needs: Determine if your project requires the construction of complex objects with multiple components and configurations.
  2. Create Abstract Builder Interface: Define a clear and concise builder interface, specifying the methods for object construction. This interface acts as a contract for concrete builder classes.
  3. Implement Concrete Builders: Develop concrete builder classes that implement the methods outlined in the builder interface. Each concrete builder corresponds to a specific object variation.
  4. Utilize a Director Class: Employ a director class to coordinate the construction process, ensuring that steps are executed in the correct order.
  5. Promote Reusability: Design your builders with reusability in mind. Creating builders that can be used for multiple objects reduces code duplication.
  6. Adopt a Modular Approach: Keep your code modular by breaking down complex object construction into smaller, manageable steps.
  7. Document Your Builders: Maintain comprehensive documentation for your builders to facilitate understanding and usage by other developers.

Relations with Other Patterns — TL;DR;

The Builder Pattern exhibits relationships with various other design patterns, contributing to a broader understanding of its applications:

Factory Method

Many designs initially use the Factory Method for simplicity and customization through subclasses. Over time, they may evolve towards Abstract Factory, Prototype, or Builder to achieve greater flexibility and complexity.

Abstract Factory

Abstract Factory specializes in creating families of related objects, whereas Builder focuses on constructing complex objects step by step. Abstract Factory typically returns the product immediately, while Builder allows additional construction steps before obtaining the product.

Composite

Builder can be valuable when creating complex Composite trees because its construction steps can be programmed to work recursively.

Bridge

Builder and Bridge patterns can be combined, where the director class assumes the role of the abstraction, and different builders serve as implementations.

Singleton

Abstract Factories, Builders, and Prototypes can all be implemented as Singletons in certain scenarios, providing unique instances of these design patterns.

Conclusion

In this comprehensive exploration of the Builder design pattern, we’ve discussed its core concepts, benefits, practical implementation in Python, and its relationships with other design patterns.

By mastering the Builder Pattern, you gain a powerful tool for constructing complex objects in a structured and flexible manner. Incorporating this pattern into your toolkit enhances your ability to create efficient and maintainable code.

Hope you enjoyed the Builder pattern exploration 🙌 Happy coding! 👨‍💻

Next on the Series 🚀

Read More 📜

The Series 🧭

References

  1. Design Patterns: Elements of Reusable Object-Oriented Software (Book)
  2. refactoring.guru Builder
  3. Head First Design Patterns (Book)
  4. Wikipedia Builder pattern
  5. What is the builder pattern?
  6. Builder Design Pattern

--

--

Amir Lavasani

I delve into machine learning 🤖 and software architecture 🏰 to enhance my expertise while sharing insights with Medium readers. 📃