Introduction
The Builder Design Pattern is a creational design pattern that aids in the step-by-step construction of complex objects.
It is particularly useful when dealing with objects that have numerous optional fields.
Problem
When dealing with objects requiring a large number of parameters, constructors become unwieldy with either a big constructor or multiple constructors with all combinations of parameters.
Explanation
Analogy with House Construction
Think of an object, such as a house, which is a complex entity composed of various components like roof, window, walls, and door.
The builder design pattern allows the creation of the house step by step:
Note - Builder Form is a mediatory form
addRoof()
==> Builder FormaddWindow()
==> Builder FormaddWalls()
==> Builder FormaddDoor()
==> Builder Formbuild()
==> Completes the object creation.
StringBuilder as an Example
StringBuilder
in Java is an example of the Builder Design Pattern.After every method (
append()
,insert()
, etc.), it returns aStringBuilder
in a mediatory form.The actual object is obtained by calling
toString()
.
Custom Builder Components
Every builder design comprises four crucial components:
Target Object: This is the object we aim to create, and it should encompass numerous optional fields.
Builder Object (Abstract): The builder object incorporates all the fields of the target object and includes the steps for creating or setting these fields. The builder object may take the form of an abstract class.
Director: The director is responsible for possessing the logic to create the target object using the builder object.
Client: The client utilizes the director to obtain the target object, ensuring a streamlined process of object creation.
Student Builder Example
Let's consider the creation of a student using the builder design pattern as an example. A student may possess both essential and optional fields in this context.
Student
package builder;
import java.util.List;
public class Student {
int rollNumber;
int age;
String name;
String fatherName;
String motherName;
List<String> subjects;
public Student(StudentBuilder studentBuilder) {
this.rollNumber = studentBuilder.rollNumber;
this.age = studentBuilder.age;
this.name = studentBuilder.name;
this.fatherName = studentBuilder.fatherName;
this.motherName = studentBuilder.motherName;
this.subjects = studentBuilder.subjects;
}
@Override
public String toString() {
return "Student{" +
"rollNumber=" + rollNumber +
", age=" + age +
", name='" + name + '\'' +
", fatherName='" + fatherName + '\'' +
", motherName='" + motherName + '\'' +
", subjects=" + subjects +
'}';
}
}
StudentBuilder
The StudentBuilder will serve as an abstract class containing all the fields of the Student Object. Each setter within this class will return the current object itself.
package builder;
import java.util.List;
public abstract class StudentBuilder {
int rollNumber;
int age;
String name;
String fatherName;
String motherName;
List<String> subjects;
public StudentBuilder setRollNumber(int rollNumber) {
this.rollNumber = rollNumber;
return this;
}
public StudentBuilder setAge(int age) {
this.age = age;
return this;
}
public StudentBuilder setName(String name) {
this.name = name;
return this;
}
public StudentBuilder setFatherName(String fatherName) {
this.fatherName = fatherName;
return this;
}
public StudentBuilder setMotherName(String motherName) {
this.motherName = motherName;
return this;
}
public abstract StudentBuilder setSubjects();
public Student build(){
return new Student(this);
}
}
EngineeringStudentBuilder
package builder;
import java.util.ArrayList;
public class EngineeringStudentBuilder extends StudentBuilder{
@Override
public StudentBuilder setSubjects() {
this.subjects = new ArrayList<>();
this.subjects.add("Mathematics");
this.subjects.add("Python Programming");
this.subjects.add("Data Structures and Algorithms");
return this;
}
}
MbaStudentBuilder
package builder;
import java.util.ArrayList;
import java.util.List;
public class MbaStudentBuilder extends StudentBuilder{
@Override
public StudentBuilder setSubjects() {
this.subjects = new ArrayList<>();
this.subjects.add("Micro Economics");
this.subjects.add("Operation Management");
this.subjects.add("Business studies");
return this;
}
}
Director
package builder;
public class Director {
StudentBuilder studentBuilder;
public Director(StudentBuilder studentBuilder) {
this.studentBuilder = studentBuilder;
}
public Student createStudent(){
if (studentBuilder instanceof MbaStudentBuilder){
return this.studentBuilder.setAge(10).setFatherName("sarma").setMotherName("naga").setSubjects().build();
} else {
return this.studentBuilder.setAge(15).setFatherName("cool").setName("chetan").setSubjects().build();
}
}
}
Client
package builder;
public class Client {
public static void main(String[] args) {
Director director1 = new Director(new EngineeringStudentBuilder());
Director director2 = new Director(new MbaStudentBuilder());
Student engineeringStudent = director1.createStudent();n
Student mbaStudent = director2.createStudent();
System.out.println(engineeringStudent.toString());
System.out.println(mbaStudent.toString());
StringBuilder
}
}
Builder Design vs Decorator Design Pattern
While Builder focuses on constructing a complex object step by step, the Decorator Design Pattern is a structural pattern that involves attaching new functionalities to an object dynamically.
The Decorator design pattern is a dynamic design approach, implying that given features f1, f2,... fn, each unique combination can generate a new object variety, analogous to pizzas. If we were to illustrate this with the builder design pattern, every combination would require a separate builder, resulting in 2^n builders. Conversely, with the Decorator pattern, there are only n distinct objects/decorators needed.
Conclusion
The Builder object will have the same set of fields as the target object, and at each step of the creation process, the builder will return itself as an intermediary object.