The Builder 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:
- The Builder: which is an interface to specify the steps to create the complex object. It defines the methods for adding the different parts or characteristics of the object.
- The Director: which is the object that controls the build process using the specified Builder. He can create many different types of objects using the same sequence of steps, but with different parts or characteristics.
There are several advantages to using the Builder pattern in your code, which are:
- Encapsulation: The Builder pattern allows you to hide the construction logic of a complex object inside Builder and the Director, instead of exposing it directly to the client. This makes the code easier to understand and maintain.
- Flexibility: The Builder pattern allows you to create different representations of a complex object using the same sequence of steps. This means you can create new representations without affecting existing code.
- Reusability: The Builder pattern allows you to reuse the construction logic of a complex object in different contexts. This means that you can build similar objects using the same building logic.
- Clarity: the Builder pattern makes the code clearer and more organized, as it separates the construction logic from the system logic. This means that you can easily understand how an object is constructed and how it is used.
- Simplicity: The Builder pattern simplifies building complex objects by providing an organized way to add different parts or characteristics to an object over time.
- Clean code: By dividing the construction of a complex object into steps, it can make the code cleaner and easier to maintain as each step is isolated and easier to understand.
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:
- When you need to build objects with many optional attributes or variables, the Builder pattern can help you keep the code clear and simple, allowing you to build objects step by step.
- When you need to build complex objects that require multiple steps or specific construction rules, the Builder pattern allows you to encapsulate this logic and easily reuse it.
- When you need to build objects that represent hierarchical data structures, the Builder pattern can help you organize building objects into logical steps.
- When you need to build objects that can be dynamically modified, the Builder pattern allows you to easily add or remove elements from an object.
- When you need to create objects that consume data from an api, the pattern builder can help organize and keep the object construction logic consistent with the data returned by the api.
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:
- When you need to build complex objects that require multiple steps or specific construction rules, the Builder pattern lets you encapsulate that logic and easily reuse it.
- When you need to build objects that can be dynamically modified, the Builder pattern allows you to easily add or remove elements from an object.
- When you need to create objects that consume data from an API, the Builder pattern can help you organize and keep object construction logic consistent with the data returned by the API.
- When you need to build objects from different types of data sources (files, api, database) and need to keep the construction logic consistent, pattern builder can be a great choice.
Hope this helps, until next time.
Top comments (0)