Fluent Builder and Powering it up with Recursive Generics in Java

Arka Hazra
6 min readFeb 8, 2020

--

Before we dive deep into the specific topic,let’s discuss a little bit about builder pattern.People who are from development background will know how painful it is to create an object if the corresponding class is having a huge load of attributes.Some times in a traditional approach the class will be created with many parameterized constructors in various permutation and combination and ultimately calling one all-argument constructor from them.If you are confused about the scenario please have a look at the below code.

public Person(String firstName, String lastName) {
this(firstName, lastName, "No description available");
}

public Person(String firstName, String lastName, String description) {
this(firstName, lastName, description, -1);
}

public Person(String firstName, String lastName, String description, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.description = description;
this.age = age;
}

Here the actual object creation is getting delegated to the “all argument” constructor from the other parameterized constructors.This approach is called “The Telescopic Constructors”. Now the problem with this approach is

  • If you want to add a new parameter in the class the whole constructor chain has to be modified.
  • As the number of parameter rises its hard to maintain the code and readability gets traded off.
  • And still if you didn’t find the above two reasons appealing just think how much difficulties somebody will face to find the correct constructor they want to use.

What , why and how of Builder design pattern:

Builder pattern was originally listed as a creational design pattern in the classic “Gang of Four” patterns. The pattern evolved with the time and currently we can use it in many ways.

For a basic definition we can say that builder is used to “separate the construction of an object from its representation”.

If you see the above example we are having a class Person , and whenever we need to create Person object , we have to call any of the constructor with proper arguments(these arguments are basically the representation of our object).This approach creates more problem when we are having a mix of optional and mandatory fields in the class.

Now if we use builder pattern in this kind of scenario we can clearly delegate the object construction from actual class to the builder class. Now there are many ways to implement builder for a specific class , one of the most popular one is to use inner builder class.

Lets have a class Person which consists of two mandatory field firstName andlastName. Our Person class is also having two optional field city and aadharId. Now if we follow traditional “Telescopic Constructor” approach we will end up making some thing like below

public Person(String firstName, String lastName) {
this(firstName, lastName, "City not Known","-1");
}

public Person(String firstName, String lastName, String city) {
this(firstName, lastName, city, -1);
}
public Person(String firstName, String lastName, long aadharId) {
this(firstName, lastName, "City not Known", aadharId);
}
public Person(String firstName, String lastName, String city, long aadharId) {
this.firstName = firstName;
this.lastName = lastName;
this.city= city;
this.aadharId= aadharId;
}

Now if we go with Builder pattern for this scenario. The code for the Person class looks like:

package com.recgen;

public class Person {

private String firstName;
private String lastName;
private String city;
private long aadharId;
protected Person(PersonBuilder builder){
this.firstName=builder.firstName;
this.lastName=builder.lastName;
this.city=builder.city;
this.aadharId=builder.aadharId;
}

public String getFirstName() {
return firstName;
}

public String getLastName() {
return lastName;
}

public String getCity() {
return city;
}

public long getAadharId() {
return aadharId;
}

public static class PersonBuilder{
private String firstName;
private String lastName;
private String city;
private long aadharId;

public PersonBuilder(String firstName,String lastName){
this.firstName=firstName;
this.lastName=lastName;
}
public PersonBuilder withCity(String city){
this.city=city;
return this;
}
public PersonBuilder withAadhar(long aadharId){
this.aadharId=aadharId;
return this;
}
public Person build(){
Person person=new Person(this);

return person;

}
}


}

Points to be noted here are

  • The creation logic is delegated to PersonBuilder from Person class.
  • Client code can never access the any of the members of Person class except the getter methods. So it gives us immutability.
  • Client code can never create a Person object without the mandatory fields( firstName and lastName ).

Now the client code and the output of the client code will be something like this:

package com.recgen.run;

import com.recgen.Person;
import com.recgen.Person.PersonBuilder;
public class BuilderTest {

public static void main(String[] args) {
PersonBuilder pb=new PersonBuilder("Bruce","Wayne");
Person p=pb.withAadhar(10200).withCity("Gotham").build();
System.out.println(p.getFirstName()+" "+p.getLastName()+" "+p.getCity()+" "+p.getAadharId());

}
}

Output

Bruce Wayne Gotham 10200

Fluent Interface:

Now if you notice two methods withCity and withAadhar in PersonBuilder both of them after setting the corresponding attribute are returning the current PersonBuilder object. In the later stage this simple tweak is helping us where it hurts the most while writing code : readability and number of lines. Instead of invoking the methods on PersonBuilder object again and again we have used method chaining or method cascading while creating the actual Person object.

Person p=pb.withAadhar(10200).withCity("Gotham").build();

This approach is called the fluent interface and the builder we wrote is called a Fluent Builder.

Problem with Fluent Builder while inheritance:

Now many of you may think that this is the best way to write a Builder as it is solving most of our problem, but what if we want to create Student class which inherits from Person class. The answer may sound simple by inheriting the new StudentBuilder inner class from PersonBuilder and it will take care of the fluent methods for the parent’s member variables(i.e. withAadhar , withCity ) but it will not be as simple as that. If we are introducing extra optional attribute in Student class like hobby , in that case in StudentBuilder we will create a method like below

public StudentBuilder withHobby(String hobby){
this.hobby=hobby;
return this;
}

also we will override the PersonBuilder ’s build method in StudentBuilder

public Student build(){
Student student=new Student(this);
return student;
}

In normal eyes even though it seems correct but compiler will throw error as soon as we try to create new Student object.

Student student =  sb.withAadhar(11345)     
.withHobby("Cricket").withCity("Queens").build();

As you can see while using the fluent interface first we are calling withAadhar and we are chaining withHobby on that,code will fail while compiling as withAadhar returns PersonBuilder and we can call withHobby method only on StudentBuilder reference.

Now we will see how to solve the problem with Recursive Generics. If you want to know basics about recursive generics follow here.

Some of you may know in languages like C++ we have self types , unfortunately java does not provide that support. So we will try to mimic self type with the help of Recursive Generics and solve this problem.For that we need to change the PersonBuilder class like below

public static class PersonBuilder<SELF extends PersonBuilder<SELF>>{
private String firstName;
private String lastName;
private String city;
private long aadharId;

public PersonBuilder(String firstName,String lastName){
this.firstName=firstName;
this.lastName=lastName;
}
public SELF withCity(String city){
this.city=city;
return (SELF)this;
}
public SELF withAadhar(long aadharId){
this.aadharId=aadharId;
return (SELF)this;
}
public Person build(){
Person person=new Person(this);

return person;

}

Points to be noted here are

  • We added generics in the signature of the PersonBuilder class.
  • We also bounded the SELF type to be a subclass of PersonBuilder with SELF extends PersonBuilder<SELF> .
  • Now the methods(withAadhar , withCity) are returning SELF instead of PersonBuilder .

Lets have a look at the Student class which is having two extra attributes , degree as mandatory and hobby as optional.

package com.recgen;

public class Student extends Person {
private String degree;
private String hobby;

public String getDegree(){
return degree;
}

public String getHobby() {
return hobby;
}
protected Student(StudentBuilder builder){
super(builder);
this.degree=builder.degree;
this.hobby=builder.hobby;

}
public static class StudentBuilder extends PersonBuilder<StudentBuilder>{
private String degree;
private String hobby;
public StudentBuilder (String firstName,String lastName,String degree){
super(firstName,lastName);
this.degree=degree;
}
public StudentBuilder withHobby(String hobby){
this.hobby=hobby;
return this;
}
public Student build(){
Student student=new Student(this);
return student;
}

}
}

Here you can seeStudentBuilderis extendingPersonBuilder<StudentBuilder> instead of PersonBuilder .

Client code will follow like this:

package com.recgen.run;

import com.recgen.Student;
import com.recgen.Student.StudentBuilder;
public class BuilderTest {

public static void main(String[] args) {
StudentBuilder sb=new StudentBuilder("Peter","Parker","under -grad");
Student s=sb.withAadhar(213)
.withHobby("hobby").withCity("Queens").build();
System.out.println(s.getFirstName()+" "+s.getLastName()+" "+s.getCity()+" "+s.getAadharId()+" "+s.getDegree()+" "+s.getHobby());


}
}

Output

Peter Parker Queens 213 under-grad hobby

If you want to try out the same code you can simply copy the Person , Student and BuilderTest class and copy it to the src of your project in eclipse ,the package structure will automatically get created.

--

--

Arka Hazra

Software Developer in a leading Fintech company.. Tech enthusiast.. Always open for new thoughts