Pattern — Builder
The pattern was originally described by Gamma et al. in his book “Design Patterns: Elements of Reusable Object-Oriented Software” (1994). In it, the authors present the?Builder?pattern as a way to separate the construction of a complex object from its representation, so that the same construction process can create different representations.
The?Builder?pattern is a build pattern that focuses on separating the construction of a complex object from its representation, so that the same build process can create different representations. It lets you build objects step by step, adding different parts or characteristics to them over time.
In general,?Builder?is used when you want to create complex objects that have many different parts or configurations and that are built through several steps.?Builder?provides an interface for specifying these steps and adding the appropriate parts or characteristics to the object.
There are two main parts involved in the?Builder?pattern, which are:
There are several advantages to using the?Builder?pattern in your code, which are:
Below is a simple code example using the?Builder?pattern.
class SandwichBuilder {
constructor() {
this.sandwich = new Sandwich();
}
addBread() {
this.sandwich.hasBread = true;
}
addCheese() {
this.sandwich.hasCheese = true;
}
addLettuce() {
this.sandwich.hasLettuce = true;
}
addMeat() {
this.sandwich.hasMeat = true;
}
getSandwich() {
return this.sandwich;
}
}
class Sandwich {
constructor() {
this.hasBread = false;
this.hasCheese = false;
this.hasLettuce = false;
this.hasMeat = false;
}
}
class SandwichMaker {
constructor() {
this.builder = null;
}
setBuilder(builder) {
this.builder = builder;
}
makeSandwich() {
this.builder.addBread();
this.builder.addCheese();
this.builder.addLettuce();
this.builder.addMeat();
}
}
const sandwichBuilder = new SandwichBuilder();
const sandwichMaker = new SandwichMaker();
sandwichMaker.setBuilder(sandwichBuilder);
sandwichMaker.makeSandwich();
const mySandwich = sandwichBuilder.getSandwich();
console.log(mySandwich);
The?SandwichBuilder?class is responsible for building an instance of the?Sandwich?class by adding different parts. It has methods for adding bread, cheese, lettuce, and meat, and a “getSandwich” method that returns the constructed sandwich object.
The?Sandwich?class is a template class that represents the sandwich object. The constructor initializes the object with all its attributes as false, and the sandwichBuilder’s addition methods will change the values.
The?SandwichMaker?class is responsible for using the?SandwichBuilder?object and making the sandwich, it is an object that controls the construction process using the specified Builder. The class has a method to set a Builder?(sandwichBuilder)?and another method to make the sandwich(makeSandwich).
Finally we start a new instance of the?SandwichBuilder?class called?sandwichBuilder. Next, we create a new instance of the?SandwichMaker?class called?sandwichMaker.
After that, we configure the?sandwichMaker?to use the?sandwichBuilder?specified with the?setBuilder(sandwichBuilder)?method. This means that the?SandwichMaker?class will use the?sandwichBuilder?to build the sandwich.
Finally, we use the?makeSandwich()?method of the?sandwichMaker?to build the sandwich and store the result in the?mySandwich?variable. Finally, we use the console.log to print the constructed sandwich to the screen. The result would be a sandwich object with all its attributes set to true.
Simple, right?
Imagine another scenario in which your team manager informed you that there is a need to consume a zip code API to automatically fill in the user’s address in the HTML form.
To solve the following problem follow the example below:
class AddressBuilder {
constructor() {
this.address = {};
}
addCEP(cep) {
this.address.cep = cep;
}
addState(state) {
this.address.state = state;
}
addCity(city) {
this.address.city = city;
}
addStreet(street) {
this.address.street = street;
}
getAddress() {
return this.address;
}
}
class CepFetcher {
static url = 'https://brasilapi.com.br/api/cep/v1'
constructor(builder) {
this.builder = builder;
}
async fetchData(cep) {
const { cep, state, city, street } = await fetch(`${url}/${this.cep}`).then(response => response.json())
const data = { cep, state, city, street };
this.builder.addCEP(data.cep);
this.builder.addState(data.state);
this.builder.addCity(data.city);
this.builder.addStreet(data.street);
}
}
const builder = new AddressBuilder();
const fetcher = new CepFetcher(builder);
fetcher.fetchData('01001000');
const address = builder.getAddress();
console.log(address);
The?AddressBuilder?class is responsible for building the address object. It has an empty constructor method that creates an empty address object. It also has methods for adding zip, state, city, and street to the data structure, and a?getAddress()?method that returns the complete address object.
The?CepFetcher?class is used to get address information from a ZIP Code API using the URL of the API. It has a builder method that takes an instance of?AddressBuilder?and a?fetchData()?method that uses the provided zip code to get the data from the API using fetch() . It then de-structures the API response to extract the zip, state, city, and street data. This data is then passed to the corresponding methods of the?AddressBuilder?class to add it to the data structure. Finally, the?fetchData?method returns the complete address object when calling the?getAddress()?method.
There are some common situations where the Builder pattern is useful, which are:
Conclusion
The?Builder?pattern allows you to keep your code clean and simple, especially when working with complex objects with many optional attributes or variables.
Builder?can be useful for many situations, some of them include:
Hope this helps, until next time.