Core Idea
The Builder pattern separates the construction process of a complex object from its representation, so that the same construction process can create different representations. This pattern is particularly suitable for creating complex objects that contain multiple components.
Why Do We Need the Builder Pattern?
Problems with Traditional Construction Methods
Let's first look at an example without using the Builder pattern:
Example
class Computer:
def __init__ (self, cpu, memory, storage, graphics_card, monitor):
self.cpu= cpu
self.memory= memory
self.storage= storage
self.graphics_card= graphics_card
self.monitor= monitor
def __str__ (self):
return f"Computer: CPU={self.cpu}, Memory={self.memory}GB, Storage={self.storage}GB, Graphics={self.graphics_card}, Monitor={self.monitor}"
# Create computer object
computer = Computer("Intel i7",16,512,"NVIDIA RTX 3060","27inch 4K")
print(computer)
Disadvantages of this method:
- Constructor has too many parameters, difficult to maintain
- Must remember the order of all parameters
- Creation process is not flexible enough
- Poor code readability
Components of the Builder Pattern
The Builder pattern consists of four main roles:
1. Product
The complex object to be created
2. Builder
Defines the abstract interface for creating the various parts of the product
3. ConcreteBuilder
Implements the Builder interface, constructs and assembles the various parts
4. Director
Constructs an object using the Builder interface
Complete Code Implementation
Let's fully implement the Builder pattern through an example of computer configuration:
1. Product Class
Example
class Computer:
"""Computer product class"""
def __init__ (self):
self.cpu=None
self.memory=None
self.storage=None
self.graphics_card=None
self.monitor=None
def __str__ (self):
specs =[]
if self.cpu:
specs.append(f"CPU: {self.cpu}")
if self.memory:
specs.append(f"RAM: {self.memory}GB")
if self.storage:
specs.append(f"Storage: {self.storage}GB")
if self.graphics_card:
specs.append(f"GPU: {self.graphics_card}")
if self.monitor:
specs.append(f"Monitor: {self.monitor}")
return"Computer configuration:n" + "n".join(f" - {spec}"for spec in specs)
2. Abstract Builder
Example
from abc import ABC, abstractmethod
class ComputerBuilder(ABC):
"""Abstract computer builder class"""
def __init__ (self):
self.computer= Computer()
@abstractmethod
def build_cpu(self):
pass
@abstractmethod
def build_memory(self):
pass
@abstractmethod
def build_storage(self):
pass
@abstractmethod
def build_graphics_card(self):
pass
@abstractmethod
def build_monitor(self):
pass
def get_computer(self):
return self.computer
3. Concrete Builder
Example
class GamingComputerBuilder(ComputerBuilder):
"""Gaming computer builder"""
def build_cpu(self):
self.computer.cpu="Intel i9-13900K"
def build_memory(self):
self.computer.memory=32
def build_storage(self):
self.computer.storage=2000# 2TB
def build_graphics_card(self):
self.computer.graphics_card="NVIDIA RTX 4090"
def build_monitor(self):
self.computer.monitor="32inch 4K 144Hz"
class OfficeComputerBuilder(ComputerBuilder):
"""Office computer builder"""
def build_cpu(self):
self.computer.cpu="Intel i5-13400"
def build_memory(self):
self.computer.memory=16
def build_storage(self):
self.computer.storage=512
def build_graphics_card(self):
self.computer.graphics_card="Integrated GPU"
def build_monitor(self):
self.computer.monitor="24inch 1080P"
4. Director
Example
class ComputerDirector:
"""Computer builder director"""
def __init__ (self, builder):
self.builder= builder
def construct_computer(self):
"""Complete process of building a computer"""
self.builder.build_cpu()
self.builder.build_memory()
self.builder.build_storage()
self.builder.build_graphics_card()
self.builder.build_monitor()
def get_computer(self):
return self.builder.get_computer()
Usage Example
Now let's see how to use the Builder pattern to create different types of computers:
Example
def main():
print("=== Builder pattern demonstration ===n")
# Create gaming computer
print("1. Build gaming computer:")
gaming_builder = GamingComputerBuilder()
director = ComputerDirector(gaming_builder)
director.construct_computer()
gaming_computer = director.get_computer()
print(gaming_computer)
print("n" + "="*50 + "n")
# Create office computer
print("2. Build office computer:")
office_builder = OfficeComputerBuilder()
director = ComputerDirector(office_builder)
director.construct_computer()
office_computer = director.get_computer()
print(office_computer)
print("n" + "="*50 + "n")
# Create custom computer
print("3. Custom computer build:")
custom_builder = CustomComputerBuilder()
custom_builder.build_cpu("AMD Ryzen 7 7800X3D")
custom_builder.build_memory(64)
custom_builder.build_storage(4000)
custom_builder.build_graphics_card("AMD RX 7900 XTX")
custom_builder.build_monitor("34inch curved ultrawide monitor")
custom_computer = custom_builder.get_computer()
print(custom_computer)
# Custom builder (optional step construction)
class CustomComputerBuilder:
"""Custom computer builder"""
def __init__ (self):
self.computer= Computer()
def build_cpu(self, cpu):
self.computer.cpu= cpu
return self# Returns self, supports method chaining
def build_memory(self, memory):
self.computer.memory= memory
return self
def build_storage(self, storage):
self.computer.storage= storage
return self
def build_graphics_card(self, graphics_card):
self.computer.graphics_card= graphics_card
return self
def build_monitor(self, monitor):
self.computer.monitor= monitor
return self
def get_computer(self):
return self.computer
if __name__ =="__main__":
main()
Variations of the Builder Pattern
1. Fluent Interface Builder
Example
class Pizza:
def __init__ (self):
self.size=None
self.cheese=False
self.pepperoni=False
self.mushrooms=False
def __str__ (self):
ingredients =[]
if self.cheese: ingredients.append("Cheese")
if self.pepperoni: ingredients.append("Italian sausage")
if self.mushrooms: ingredients.append("Mushroom")
return f"{self.size}inch pizza, toppings: {', '.join(ingredients)}"
class PizzaBuilder:
def __init__ (self, size):
self.pizza= Pizza()
self.pizza.size= size
def add_cheese(self):
self.pizza.cheese=True
return self
def add_pepperoni(self):
self.pizza.pepperoni=True
return self
def add_mushrooms(self):
self.pizza.mushrooms=True
return self
def build(self):
return self.pizza
# Using fluent interface
pizza =(PizzaBuilder(12)
.add_cheese()
.add_pepperoni()
.add_mushrooms()
.build())
print(pizza)
Advantages and Disadvantages of the Builder Pattern
Advantages
| Advantage | Description |
|---|---|
| Good encapsulation | Construction process is separated from representation, clients don't need to know internal details |
| Strong extensibility | New concrete builders can be easily added |
| Better control | More fine-grained control over the construction process |
| Code readability | Code is clearer when using chained calls |
Disadvantages
| Disadvantage | Description |
|---|---|
| Increased complexity | Requires defining multiple classes, increasing system complexity |
| Not suitable for large product differences | If products differ greatly, the Builder pattern is not very suitable |
| Builder depends on product | Builders need to know the specific details of the product |
Applicable Scenarios
Situations suitable for using the Builder pattern:
- Creating complex objects: Need to create complex objects containing multiple components
- Stable construction process: The object's construction process is relatively stable, but the object's components change frequently
- Multiple representations: The object to be created has multiple different representations
- Avoiding telescoping constructors: Avoid using constructors with many parameters
Practical application examples:
- Document converters: Converting documents to different formats such as PDF, Word, HTML
- Meal preparation: Preparing meals with different combinations (burger, fries, drink)
- UI component building: Building complex user interface components
- Report generation: Generating reports containing different parts (header, data, charts)
Practice Exercises
Exercise 1: Implement a Car Builder
Try to implement a Car Builder pattern, containing the following components:
- Engine (gasoline, electric, hybrid)
- Body color (red, blue, black, white)
- Wheel size (16-inch, 18-inch, 20-inch)
- Interior (leather, fabric)
Exercise 2: Improve the Computer Builder
Add the following features to the computer builder:
- Optional components (e.g., whether to install an optical drive)
- Validation logic (e.g., memory cannot be less than 4GB)
- Price calculation functionality
Summary
The Builder pattern is a powerful creational design pattern. By decomposing the construction process of a complex object into multiple simple steps, it makes the creation process more flexible and controllable. Although it increases system complexity, in scenarios where complex objects need to be created and the construction process is relatively fixed, the Builder pattern can significantly improve code maintainability and extensibility.
Remember the core idea of design patterns: not to use patterns for the sake of using patterns, but to solve specific design problems. In actual development, whether to use the Builder pattern should be decided based on specific requirements.
YouTip