The Builder Design Pattern is a creational pattern that simplifies the construction of complex objects.
It separates the construction of an object from its representation, allowing the same construction process to create different representations.
This pattern is particularly useful when you have complex objects with many optional or required configurations.
It improves readability and maintainability by using method chaining and clearly defined construction steps.
- Clarity: Clearly separates object creation from its use.
- Flexibility: Enables creating different object configurations using the same construction process.
- Immutability: Often produces immutable objects, enhancing safety and predictability.
- An object has many attributes or configurations.
- The object construction is complex and requires step-by-step creation.
- You want a clear, fluent interface for object creation.
Let's illustrate this pattern with an example of creating a customizable Pizza
object.
Define the complex object you want to build:
class Pizza {
size: string;
cheese: boolean;
pepperoni: boolean;
mushrooms: boolean;
constructor(builder: PizzaBuilder) {
this.size = builder.size;
this.cheese = builder.cheese;
this.pepperoni = builder.pepperoni;
this.mushrooms = builder.mushrooms;
}
describe(): void {
console.log(`Pizza [Size: ${this.size}, Cheese: ${this.cheese}, Pepperoni: ${this.pepperoni}, Mushrooms: ${this.mushrooms}]`);
}
}
Implement the builder that handles step-by-step object construction:
class PizzaBuilder {
size: string;
cheese: boolean = false;
pepperoni: boolean = false;
mushrooms: boolean = false;
constructor(size: string) {
this.size = size;
}
addCheese(): PizzaBuilder {
this.cheese = true;
return this;
}
addPepperoni(): PizzaBuilder {
this.pepperoni = true;
return this;
}
addMushrooms(): PizzaBuilder {
this.mushrooms = true;
return this;
}
build(): Pizza {
return new Pizza(this);
}
}
Here's how a client can use the builder to create pizzas:
// Create a pizza with cheese and pepperoni
const pizza1 = new PizzaBuilder('Large')
.addCheese()
.addPepperoni()
.build();
pizza1.describe();
// Create a vegetarian pizza
const pizza2 = new PizzaBuilder('Medium')
.addCheese()
.addMushrooms()
.build();
pizza2.describe();
Output:
Pizza [Size: Large, Cheese: true, Pepperoni: true, Mushrooms: false]
Pizza [Size: Medium, Cheese: true, Pepperoni: false, Mushrooms: true]
- Easy to manage optional and mandatory attributes clearly.
- Creates highly readable code with method chaining.
- Simplifies the construction of complex objects without constructors with many parameters.
To extend the builder with additional options (e.g., adding olives), simply:
- Update the builder class:
addOlives(): PizzaBuilder {
(this as any).olives = true;
return this;
}
- Update the product class if needed:
class Pizza {
// ...existing properties
olives: boolean;
constructor(builder: PizzaBuilder) {
// ...existing assignments
this.olives = (builder as any).olives || false;
}
describe(): void {
console.log(`Pizza [Size: ${this.size}, Cheese: ${this.cheese}, Pepperoni: ${this.pepperoni}, Mushrooms: ${this.mushrooms}, Olives: ${this.olives}]`);
}
}
Client usage remains fluent:
const pizza3 = new PizzaBuilder('Small')
.addCheese()
.addOlives()
.build();
pizza3.describe();
Output:
Pizza [Size: Small, Cheese: true, Pepperoni: false, Mushrooms: false, Olives: true]
The Builder Design Pattern simplifies constructing complex objects step by step. It provides an intuitive, fluent, and readable approach, making object creation flexible, scalable, and maintainable.