The Builder design pattern is one of the creational patterns in software design. It's particularly useful when creating complex objects that require a lot of parameters, many of which are optional. The pattern separates the construction of an object from its representation, allowing the same construction process to create different representations.
In this tutorial, we'll explore how the Builder pattern works and provide practical examples to help you understand and implement it in your projects.
The main idea behind the Builder pattern is to encapsulate the construction logic into a separate builder class. This allows for more flexible object creation, especially when dealing with complex objects that have multiple optional parameters or require different configurations.
Let's dive into some examples to illustrate how the Builder pattern can be implemented in practice.
Suppose we are building a house and want to use the Builder pattern to encapsulate the construction process. We'll define a House class, a HouseBuilder interface, a concrete builder, and a director.
class House {
constructor(walls, roof, doors, windows) {
this.walls = walls;
this.roof = roof;
this.doors = doors;
this.windows = windows;
}
toString() {
return `House with ${this.walls} walls, ${this.roof} roof, ${this.doors} door(s), and ${this.windows} window(s).`;
}
}
#### Step 2: Define the Builder Interface
```jsx
class HouseBuilder {
buildWalls() {}
buildRoof() {}
addDoors() {}
addWindows() {}
getResult() {}
}
class SimpleHouseBuilder extends HouseBuilder {
constructor() {
this.reset();
}
reset() {
this.house = new House(0, '', 0, 0);
}
buildWalls(wallCount) {
this.house.walls = wallCount;
}
buildRoof(type) {
this.house.roof = type;
}
addDoors(doorCount) {
this.house.doors = doorCount;
}
addWindows(windowCount) {
this.house.windows = windowCount;
}
getResult() {
const result = this.house;
this.reset();
return result;
}
}
class HouseDirector {
constructor(builder) {
this.builder = builder;
}
constructBasicHouse() {
this.builder.buildWalls(4);
this.builder.buildRoof('gable');
this.builder.addDoors(1);
this.builder.addWindows(2);
}
}
const builder = new SimpleHouseBuilder();
const director = new HouseDirector(builder);
director.constructBasicHouse();
const house = builder.getResult();
console.log(house.toString());
// Output: House with 4 walls, gable roof, 1 door(s), and 2 window(s).
Let's consider another example where we build a pizza using the Builder pattern.
class Pizza {
constructor(size, crust, toppings) {
this.size = size;
this.crust = crust;
this.toppings = toppings;
}
toString() {
return `Pizza with ${this.size} size, ${this.crust} crust, and toppings: ${this.toppings.join(', ')}.`;
}
}
class PizzaBuilder {
setSize(size) {}
setCrust(crust) {}
addTopping(topping) {}
getResult() {}
}
class MargheritaPizzaBuilder extends PizzaBuilder {
constructor() {
this.reset();
}
reset() {
this.pizza = new Pizza('medium', 'thin', []);
}
setSize(size) {
this.pizza.size = size;
}
setCrust(crust) {
this.pizza.crust = crust;
}
addTopping(topping) {
this.pizza.toppings.push(topping);
}
getResult() {
const result = this.pizza;
this.reset();
return result;
}
}
const builder = new MargheritaPizzaBuilder();
builder.setSize('large');
builder.setCrust('thick');
builder.addTopping('mozzarella');
builder.addTopping('basil');
const pizza = builder.getResult();
console.log(pizza.toString());
// Output: Pizza with large size, thick crust, and toppings: mozzarella, basil.
In the next section, we'll explore another creational pattern called the Prototype Pattern. The Prototype pattern allows you to create new objects by copying existing ones, which can be particularly useful when object creation is complex or resource-intensive.
Stay tuned for more insights into design patterns and how they can help you write better software!