The builder pattern is a creational pattern which allows to create complex objects in an easy way. You can create multiple representations of the objects step by step using the same construction code.
You can find the full example source code here.
In our example we are going to define a Builder which will create cars and motorcycles. The example has been simplified to focus on the pattern.
First, we are going to create a interface for the Builder which will define the different operations needed to create our object. Then, we will create a concrete builder which will implement the interface and will implement the operations.
It would be possible to have multiple concrete builders in case that we wanted
to create different types of objects with the same construction process. For
example, following our example we could have a CarBuilder
and a
MotorcycleBuilder
.
As a last step, we will see how we can add a Director class where we can define the different construction processes so that they have to be defined only once. The Director is not necessary, you can always use the builder directly from the client code.
Diagram of the builder pattern. Credit: Wikimedia Commons
In our case, the Builder will create cars and motorcycles. The construction
process will have only two steps: setSeats
and setEngine
. Reminder: this
example has been simplified to focus on learning the pattern. In a real world
example, the creation process would have more steps and it would be much more
complex.
interface Builder {
setSeats(seats: number): this;
setEngine(engine: string): this;
}
In our example we are only going to have two concrete Builders, the CarBuilder
and the MotorcycleBuilder
.
class CarBuilder implements Builder {
private car: Car;
constructor() {
this.car = new Car();
}
public setSeats(seats: number): this {
this.car.setSeats(seats);
return this;
}
public setEngine(engine: string): this {
this.car.setEngine(engine);
return this;
}
public getResult(): Car {
return this.car;
}
}
class MotorcycleBuilder implements Builder {
private motorcycle: Motorcycle;
constructor() {
this.motorcycle = new Motorcycle();
}
public setSeats(seats: number): this {
this.motorcycle.setSeats(seats);
return this;
}
public setEngine(engine: string): this {
this.motorcycle.setEngine(engine);
return this;
}
public getResult(): Motorcycle {
return this.motorcycle;
}
}
Just with this code it would be enough to implement the Builder pattern. We
could create a Car
or a Motorcycle
using the CarBuilder
or the
MotorcycleBuilder
directly from the client code.
const car = new CarBuilder().setSeats(2).setEngine("V-12").getResult();
const motorcycle = new MotorcycleBuilder()
.setSeats(2)
.setEngine("V-4")
.getResult();
We are going to go one step further and create the Director
class. The
Director
will contain the different construction processes of the object
representations that we need in our app.
In our example we are going to define 2 construction processes for the car and 2 for the motorcycle.
class Director {
public buildFerrari(): Car {
return new CarBuilder().setSeats(2).setEngine("V-12").getResult();
}
public buildToyota(): Car {
return new CarBuilder().setSeats(7).setEngine("V-6").getResult();
}
public buildHonda(): Motorcycle {
return new MotorcycleBuilder().setSeats(2).setEngine("V-4").getResult();
}
public buildYamaha(): Motorcycle {
return new MotorcycleBuilder().setSeats(1).setEngine("V-2").getResult();
}
}
And the Director can be used like this:
const director = new Director();
director.buildFerrari();
director.buildToyota();
director.buildHonda();
director.buildYamaha();
As always, make sure it makes sense to use this pattern in your application. Otherwise you could be introducing unnecessary complexity.