Low-level design2 min
Builder Pattern
Type: Creational Pattern
Relevance: High. Widely used to solve complex constructor problems.
The Builder pattern lets you construct complex objects step by step. It allows you to produce different types and representations of an object using the same construction code.
Real-Life Analogy
Think of ordering a Custom Pizza.
A pizza can have many optional components: cheese, pepperoni, mushrooms, olives, extra sauce, stuffed crust.
If you tried to order this using a single command (a constructor), you'd have to specify every single parameter: new Pizza(true, true, false, false, true, "stuffed").
Instead, the waiter acts as a builder: "I want a pizza. Add cheese. Add pepperoni. Make it stuffed crust. That's it, build it."
The Problem (The Telescoping Constructor)
When a class has many optional attributes, developers often create a massive constructor:
class House {
constructor(
public windows: number,
public doors: number,
public hasGarage?: boolean,
public hasPool?: boolean,
public hasStatues?: boolean,
public hasGarden?: boolean
) {}
}
// Creating a house with a pool but no garage or statues:
// Look at how ugly and confusing this is!
const myHouse = new House(4, 2, undefined, true, undefined, undefined);
How to Implement Builder
- Extract the object construction code out of its own class and move it to separate objects called Builders.
- The Builder provides step-by-step methods (
buildWalls(),addPool()). - (Optional) Create a Director class that executes the building steps in a specific sequence to produce popular configurations.
Example in Code
class House {
public parts: string[] = [];
public listParts(): void {
console.log(`House components: ${this.parts.join(', ')}`);
}
}
interface Builder {
buildWalls(): void;
buildDoors(): void;
buildRoof(): void;
buildPool(): void;
}
class ModernHouseBuilder implements Builder {
private house: House;
constructor() {
this.house = new House();
}
public reset(): void { this.house = new House(); }
public buildWalls(): void { this.house.parts.push("Glass Walls"); }
public buildDoors(): void { this.house.parts.push("Sliding Doors"); }
public buildRoof(): void { this.house.parts.push("Flat Roof"); }
public buildPool(): void { this.house.parts.push("Infinity Pool"); }
public getResult(): House {
const result = this.house;
this.reset(); // Reset for next build
return result;
}
}
// Client Code
const builder = new ModernHouseBuilder();
// Step-by-step construction! No massive constructor needed.
builder.buildWalls();
builder.buildRoof();
builder.buildPool();
const myHouse = builder.getResult();
myHouse.listParts(); // Glass Walls, Flat Roof, Infinity Pool
Note on Method Chaining (Fluent Interface)
In modern languages (like TypeScript/Java), builders are often implemented with method chaining by returning this at the end of every step.
// Fluent Builder Usage Example:
const house = new HouseBuilder()
.setWalls("Glass")
.setRoof("Flat")
.addPool()
.build();
Why is it Important?
- Readability: Replaces massive, confusing constructors with clear, readable step-by-step method calls.
- Immutability: You can configure the entire object using the builder, and when you call
.build(), it returns a fully instantiated, immutable object.