Fluent Builder and Powering it up with Recursive Generics in Java
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
fromPerson
class. - Client code can never access the any of the members of
Person
class except thegetter
methods. So it gives us immutability. - Client code can never create a
Person
object without the mandatory fields(firstName
andlastName
).
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 ofPersonBuilder
withSELF extends PersonBuilder<SELF>
. - Now the methods(
withAadhar
,withCity
) are returningSELF
instead ofPersonBuilder
.
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 seeStudentBuilder
is 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.