Table of contents
Hi Everyone, welcome once again to the Design Pattern series, I hope you are finding this helpful. In today's, article we will learn about the Builder Design Pattern, understand why it is required, and implement it in Java.
What is a Builder Design Pattern?
The Builder Design Pattern is a creational design pattern that helps in creating complex objects in a step-by-step approach. It separates the construction of a complex object from its representation, allowing the same construction process to create different representations.
Let us understand the above explanation by an example:
Suppose, you have a class Person
and there are multiple attributes like name, age, address, and phone number. Out of these, only the name is mandatory, others are optional attributes.
Suppose, we need to create 3 objects of class Person
Contains only name
Contains name and age
Contains name, age, and phone number
Contains all attributes
public class Person {
private String name;
private int age;
private String address;
private String phoneNumber;
// Constructor with only mandatory attributes
public Person(String name) {
this.name = name;
}
// Constructor with age
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Constructor with age and address
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
// Constructor with all attributes
public Person(String name, int age, String address, String phoneNumber) {
this.name = name;
this.age = age;
this.address = address;
this.phoneNumber = phoneNumber;
}
// Getters and other methods...
}
If we don't use the Builder pattern, we might end up using multiple constructors (constructor overloading) or a large constructor with numerous parameters, which can lead to confusion, errors, and difficulty in managing and maintaining the code.
So, the Builder Pattern is useful when there are many parameters in the object's constructor, and some of them are optional. The pattern makes the code more readable, flexible, and maintainable. With this, we can create multiple representations of a class using the same code.
Here's an example of the Builder pattern in Java:
Implementation
public class Computer {
//Required parameters
private String processor;
private String ram;
//Optional parameters
private String graphicsCard;
private String ssd;
private Computer(ComputerBuilder computerBuilder) {
this.processor = computerBuilder.processor;
this.ram = computerBuilder.ram;
this.graphicsCard = computerBuilder.graphicsCard;
this.ssd = computerBuilder.ssd;
}
public void setProcessor(String processor) {
this.processor = processor;
}
public void setRam(String ram) {
this.ram = ram;
}
public void setGraphicsCard(String graphicsCard) {
this.graphicsCard = graphicsCard;
}
public void setSsd(String ssd) {
this.ssd = ssd;
}
@Override
public String toString() {
return "Computer{" +
"processor='" + processor + '\'' +
", ram='" + ram + '\'' +
", graphicsCard='" + graphicsCard + '\'' +
", ssd='" + ssd + '\'' +
'}';
}
public static class ComputerBuilder {
private String processor;
private String ram;
private String graphicsCard;
private String ssd;
public ComputerBuilder(String processor, String ram) {
this.processor = processor;
this.ram = ram;
}
public ComputerBuilder setGraphicsCard(String graphicsCard) {
this.graphicsCard = graphicsCard;
return this;
}
public ComputerBuilder setSsd(String ssd) {
this.ssd = ssd;
return this;
}
public Computer build() {
return new Computer(this);
}
}
}
Computer
Class:Represents a computer with some required parameters (processor, RAM) and optional parameters (graphicsCard, SSD).
Contains a private constructor that takes an
ComputerBuilder
as a parameter and initializes the fields using the builder.
ComputerBuilder
Class (Static Inner Class):Serves as the builder class responsible for constructing
Computer
objects.Has private fields for the same parameters as the
Computer
class: processor, RAM, graphicsCard, and SSD.Has a constructor that takes the required parameters (processor, RAM).
There are setter methods for the optional attributes, that returns the instance of
ComputerBuilder
(we will understand this below)Contains a
build
method that creates a newComputer
object using the provided builder instance.
Usage
public class BuilderDemo { public static void main(String[] args) { Computer computer1 = new Computer.ComputerBuilder("Intel i7", "16 GB") .setSsd("256 GB") .build(); System.out.println("Computer1 - " + computer1); Computer computer2 = new Computer.ComputerBuilder("Intel i9", "32 GB") .setSsd("1 TB") .setGraphicsCard("NVIDIA GTX 1660") .build(); System.out.println("Computer2 - " + computer2); } }
If we focus on the creation of
computer1
object, we are passing the mandatory attributes to the constructor ofComputerBuilder
which are processor and RAM.//This part of the code, returns the ComputerBuilder //and it has values of processor and RAM new Computer.ComputerBuilder("Intel i7", "16 GB")
Now we want to add SSD to the same computer, so we used
new Computer.ComputerBuilder("Intel i7", "16 GB") .setSsd("256 GB") //Now for the same ComputerBuilder which has the values of processor and RAM //we are add the SSD public ComputerBuilder setSsd(String ssd) { this.ssd = ssd; return this; } //If we see the setter method, this returns the same ComputerBuilder //because we can add other attributes to the same builder like we did for SSD
Now that all the attributes required to build the computer are added in the ComputerBuilder, we will now construct the actual object
Computer computer1 = new Computer.ComputerBuilder("Intel i7", "16 GB") .setSsd("256 GB") .build(); //when the build() method of the builder class is called, it internally calls the //private constructor of the Computer class, in which we set the attributes of Computer class from the builder class obejct private Computer(ComputerBuilder computerBuilder) { this.processor = computerBuilder.processor; this.ram = computerBuilder.ram; this.graphicsCard = computerBuilder.graphicsCard; this.ssd = computerBuilder.ssd; }
Similarly,
computer2
object is created which also includes the graphics cardOutput
Computer1 - Computer{processor='Intel i7', ram='16 GB', graphicsCard='null', ssd='256 GB'} Computer2 - Computer{processor='Intel i9', ram='32 GB', graphicsCard='NVIDIA GTX 1660', ssd='1 TB'}
So, By using the Builder pattern, we can create
Computer
objects with different combinations of required and optional parameters without having to create multiple constructors, making the code more readable and maintainable.
Director
The Director concept is an additional component in the Builder pattern, that is responsible for managing the construction process of an object. It is like a manager responsible for creating complex objects step by step.
The Director works with the Builder to construct the object, while the Builder focuses on constructing the parts of the object.
Imagine you have a set of LEGO bricks and you want to build different models using the same bricks. The Builder is like the actual bricks, and the Director is like the person(Civil Engineer) who decides how to create different models using the available bricks.
The Director takes a builder instance, sets the configuration details, and then constructs the object using the builder. This approach allows you to create different representations of an object using the same construction process (eg: gaming computer or business computer)
The Director concept can be useful when you want to encapsulate the process of building an object with different configurations or when you want to reuse the construction process for different representations.
In the context of the Computer
example, let's create a ComputerDirector
class that takes a ComputerBuilder
instance and provides methods to create different types of computers, such as a gaming computer or a business computer. Here's the ComputerDirector
class:
public class ComputerDirector {
private Computer.ComputerBuilder computerBuilder;
public void setComputerBuilder(Computer.ComputerBuilder computerBuilder) {
this.computerBuilder = computerBuilder;
}
public Computer createGamingComputer() {
return this.computerBuilder
.setGraphicsCard("Nvidea GTX 8GB")
.setSsd("1TB Nvme")
.build();
}
public Computer createBusinessComputer() {
return this.computerBuilder
.setSsd("256 GB Nvme")
.build();
}
}
In this example, the ComputerDirector
class manages the construction of gaming and business computers using a ComputerBuilder
instance. The director sets the appropriate configurations for each type of computer and then constructs the object using the builder.
public class BuilderDemo {
public static void main(String[] args) {
// 1. Creating a director
ComputerDirector computerDirector = new ComputerDirector();
// 2. Creating a Gaming Computer
Computer.ComputerBuilder gamingComputerBuilder = new Computer.ComputerBuilder("Intel i9", "32GB");
computerDirector.setComputerBuilder(gamingComputerBuilder);
Computer gamingComputer = computerDirector.createGamingComputer();
System.out.println("Gaming Computer - " + gamingComputer);
// 3. Creating a Business Computer
Computer.ComputerBuilder businessComputerBuilder = new Computer.ComputerBuilder("Intel Xeon", "16GB");
computerDirector.setComputerBuilder(businessComputerBuilder);
Computer businessComputer = computerDirector.createBusinessComputer();
System.out.println("Business Computer - " + businessComputer);
}
}
Creating a Director -
ComputerDirector
:- An instance of
ComputerDirector
is created to guide the construction process of different types of computers.
- An instance of
Creating Gaming Computer:
A
Computer.ComputerBuilder
instance (gamingComputerBuilder
) is created with mandatory attributes "Intel i9" processor and "32GB" RAM.The
computerDirector
is set to use this builder.The
createGamingComputer
method ofcomputerDirector
is called to construct a gaming computer using the provided builder.The resulting gaming computer is printed.
Creating Business Computer:
Another
Computer.ComputerBuilder
instance (businessComputerBuilder
) is created with "Intel Xeon" processor and "16GB" of RAM.The
computerDirector
is set to use this builder.The
createBusinessComputer
method ofcomputerDirector
is called to construct a business computer using the provided builder.The resulting business computer is printed.
Output
Gaming Computer - Computer{processor='Intel i9', ram='32GB', graphicsCard='Nvidea GTX 8GB', ssd='1TB Nvme'} Business Computer - Computer{processor='Intel Xeon', ram='16GB', graphicsCard='null', ssd='256 GB Nvme'}
Conclusion
In summary, the Builder design pattern provides an elegant solution to construct complex objects with multiple configuration options. By separating the construction process from the actual representation, it allows for flexibility, readability, and easier maintenance of code.
Feel free to like and share this post if you found it helpful! Any feedbacks? Please mention them in the comments.
Until we meet again, Happy Coding!
Complete implementations can be found over Github
References