Week 21 — What is the builder pattern in java and how does it work?

Question of the Week #21
What is the builder pattern in java and how does it work?
6 Replies
Eric McIntyre
Eric McIntyre2y ago
Builder pattern allow to create readable object and put into constructor values. Also is good to build mock object for unit test where can build object with specify requirements.
Submission from Seba1908#8447
Eric McIntyre
Eric McIntyre2y ago
builder pattern is a way to create objects. The most often used to create objects with many fields. If we want create biulder we have to create static class in our class or we create another class. We always implemented: * builder methods which look like setters but them give opportunity to trigger after dot another builder method. * method which build object based on values from builder methods example with static inner class
Eric McIntyre
Eric McIntyre2y ago
public class Weapon { private String type; private String name; private Integer damage; private Long durability; private List<String> perks; private Weapon(final String type, final String name, final Integer damage, final Long durability, final List<String> perks) { this.type = type; this.name = name; this.damage = damage; this.durability = durability; this.perks = perks; } public String getType() { return type; } public void setType(final String type) { this.type = type; } public String getName() { return name; } public void setName(final String name) { this.name = name; } public Integer getDamage() { return damage; } public void setDamage(final Integer damage) { this.damage = damage; } public Long getDurability() { return durability; } public void setDurability(final Long durability) { this.durability = durability; } public List<String> getPerks() { return perks; } public void setPerks(final List<String> perks) { this.perks = perks; } public static class Builder { private String type; private String name; private Integer damage; private Long durability; private List<String> perks = new ArrayList<>(); // builders methods public Builder withType(final String type) { this.type = type; return this; } public Builder withName(final String name) { this.name = name; return this; } public Builder withDamage(final Integer damage) { this.damage = damage; return this; } public Builder withDurability(final Long durability) { this.durability = durability; return this; } public Builder withPerks(final List<String> perks) { this.perks = perks; return this; } // method build aimed object public Weapon build() { return new Weapon(type, name, damage, durability, perks); } } }
Submission from Piotr Łyszkowski#0456
Eric McIntyre
Eric McIntyre2y ago
The builder pattern is making a static nested Builder class that handles the initialization of all necessary instance variables of the class the Builder is nested in. There is a method in the Builder class for each required instance variable of the outer class, all the methods (except the build method, which returns a new instance of the OuterClass with only the Builder as a parameter in its constructor) return a Builder object (basically the Builder returns itself after setting the instance variable). Here is an example:
import java.util.List;
public class OuterClass {
int instanceVariable1;
String instanceVariable2;
List<Integer> instanceVariable3;
public OuterClass(Builder build) {
instanceVariable1= build.instanceVariable1;
instanceVariable2= build.instanceVariable2;
instanceVariable3=build.instanceVariable3;
}
public static class Builder{
int instanceVariable1;
String instanceVariable2;
List<Integer> instanceVariable3;

public Builder instanceVariable1(int instanceVariable1) {
this.instanceVariable1 = instanceVariable1;
return this;
}

public Builder instanceVariable2(String instanceVariable2) {
this.instanceVariable2 = instanceVariable2;
return this;
}

public Builder instanceVariable3(List<Integer> instanceVariable3) {
this.instanceVariable3 = instanceVariable3;
return this;
}
public OuterClass build(){
return new OuterClass(this);
}
}
}
import java.util.List;
public class OuterClass {
int instanceVariable1;
String instanceVariable2;
List<Integer> instanceVariable3;
public OuterClass(Builder build) {
instanceVariable1= build.instanceVariable1;
instanceVariable2= build.instanceVariable2;
instanceVariable3=build.instanceVariable3;
}
public static class Builder{
int instanceVariable1;
String instanceVariable2;
List<Integer> instanceVariable3;

public Builder instanceVariable1(int instanceVariable1) {
this.instanceVariable1 = instanceVariable1;
return this;
}

public Builder instanceVariable2(String instanceVariable2) {
this.instanceVariable2 = instanceVariable2;
return this;
}

public Builder instanceVariable3(List<Integer> instanceVariable3) {
this.instanceVariable3 = instanceVariable3;
return this;
}
public OuterClass build(){
return new OuterClass(this);
}
}
}
public class Main {
public static void main(String[] args) {

OuterClass example= new OuterClass.Builder()
.instanceVariable1(10)
.instanceVariable2("Example")
.instanceVariable3(List.of(1, 1))
.build();
}
}
public class Main {
public static void main(String[] args) {

OuterClass example= new OuterClass.Builder()
.instanceVariable1(10)
.instanceVariable2("Example")
.instanceVariable3(List.of(1, 1))
.build();
}
}
The builder is used mainly for larger objects with more variables.
⭐ Submission from nikcho-kouhai#8153
Eric McIntyre
Eric McIntyre2y ago
If a class has many fields that have to be initialized in the constructor, maintaining that class can be tedious. For example, imagine the following class representing a person:
public class Person{
private String firstName;
private String middleName;
private String lastName;
private int age;
private int socialSecurityNumber;
private int numberOfChildren;
private boolean gender;//for the sake of demonstration, a boolean is used for the gender. In many use-cases, it is better to use an enum or similar
private boolean isMarried;
public Person(String firstName, String middleName, String lastName, int age, int socialSecurityNumber, int numberOfChildren, boolean gender, boolean isMarried){
this.firstName=Objects.requireNonNull(firstName);
this.middleName=Objects.requireNonNull(middleName);
//...
}
//getters, etc. omitted
}
public class Person{
private String firstName;
private String middleName;
private String lastName;
private int age;
private int socialSecurityNumber;
private int numberOfChildren;
private boolean gender;//for the sake of demonstration, a boolean is used for the gender. In many use-cases, it is better to use an enum or similar
private boolean isMarried;
public Person(String firstName, String middleName, String lastName, int age, int socialSecurityNumber, int numberOfChildren, boolean gender, boolean isMarried){
this.firstName=Objects.requireNonNull(firstName);
this.middleName=Objects.requireNonNull(middleName);
//...
}
//getters, etc. omitted
}
When an instance of this class is created, one needs to specify all of the attributes listed in the constructor, even if some of them are not required:
Person p = new Person("Some","","Person", 18, 1234, 0, true, false);
Person p = new Person("Some","","Person", 18, 1234, 0, true, false);
If the class has many fields, this becomes difficult to read as it might not be clear which argument corresponds to which parameter/field (most IDEs like Eclipse or IntelliJ can provide that information in visual indicators but readability of code should not be dependent on IDEs). Furthermore, it is possible that bugs slip through if the fields are ordered incorrectly. A builder allows to construct objects of a certain class by calling methods for different arguments. For each field, the builder provides a "fluent setter" which accepts the new/future value for that field and returns the instance of the builder. Returning the current instance/this object allows for chaining.
public class Person{
private final String firstName;
private final String middleName;
private final String lastName;
private final int age;
private final int socialSecurityNumber;
private final int numberOfChildren;
private final boolean gender;//for the sake of demonstration, a boolean is used for the gender. In many use-cases, it is better to use an enum or similar
private final boolean isMarried;
private Person(String firstName, String middleName, String lastName, int age, int socialSecurityNumber, int numberOfChildren, boolean gender, boolean isMarried){
this.firstName=Objects.requireNonNull(firstName);
this.middleName=Objects.requireNonNull(middleName);
//...
}
public static class Builder{
private String firstName;
private String middleName="";//default value
private String lastName;
private int age;
private int socialSecurityNumber;
private int numberOfChildren=0;
private boolean gender;
private boolean isMarried=false;
public Builder firstName(String firstName){
this.firstName=firstName;
return this;
}
public Builder middleName(String middleName){
this.middleName=middleName;
return this;
}
//methods for other fields omitted
public Person build(){
return new Person(firstName, middleName, lastName, age, socialSecurityNumber, numberOfChildren, gender, isMarried);
}
}
//getters, etc. omitted
}
public class Person{
private final String firstName;
private final String middleName;
private final String lastName;
private final int age;
private final int socialSecurityNumber;
private final int numberOfChildren;
private final boolean gender;//for the sake of demonstration, a boolean is used for the gender. In many use-cases, it is better to use an enum or similar
private final boolean isMarried;
private Person(String firstName, String middleName, String lastName, int age, int socialSecurityNumber, int numberOfChildren, boolean gender, boolean isMarried){
this.firstName=Objects.requireNonNull(firstName);
this.middleName=Objects.requireNonNull(middleName);
//...
}
public static class Builder{
private String firstName;
private String middleName="";//default value
private String lastName;
private int age;
private int socialSecurityNumber;
private int numberOfChildren=0;
private boolean gender;
private boolean isMarried=false;
public Builder firstName(String firstName){
this.firstName=firstName;
return this;
}
public Builder middleName(String middleName){
this.middleName=middleName;
return this;
}
//methods for other fields omitted
public Person build(){
return new Person(firstName, middleName, lastName, age, socialSecurityNumber, numberOfChildren, gender, isMarried);
}
}
//getters, etc. omitted
}
Eric McIntyre
Eric McIntyre2y ago
With that builder, it is possible to instantiate classes as follows:
Person person = new Person.Builder()
.firstName("Some")
.lastName("Person")
.age(18)
.socialSecurityNumber(1234)
.gender(true)
.build();
Person person = new Person.Builder()
.firstName("Some")
.lastName("Person")
.age(18)
.socialSecurityNumber(1234)
.gender(true)
.build();
Creating objects like that is often more readable with many fields of similar types and allows to omit some optional fields This is especially useful for immutable data classes (e.g. records) with many optional fields (where object creation commonly uses default values for some fields) It is also possible to create builders automatically using annotation processors but that approach makes the build process more complicated and is hard to debug if it goes wrong.
Submission from dan1st#7327
Want results from more Discord servers?
Add your server