Tuesday 29 November 2016

When to use builder pattern

When a class requires more arguments to instantiate then it is better to go for a builder pattern. Let me explain with an example.

For example, you are developing an application to maintain all the employees’ information of organization ‘ABC’. Initially, I designed Employee model class like below.

Approach 1: Design Employee model class by providing setter and getter methods.

Employee.java
public class Employee {
 private int id;
 private String firstName;
 private String lastName;
 private float salary;
 private String designation;

 public int getId() {
  return id;
 }

 public void setId(int id) {
  this.id = id;
 }

 public String getFirstName() {
  return firstName;
 }

 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public void setLastName(String lastName) {
  this.lastName = lastName;
 }

 public float getSalary() {
  return salary;
 }

 public void setSalary(float salary) {
  this.salary = salary;
 }

 public String getDesignation() {
  return designation;
 }

 public void setDesignation(String designation) {
  this.designation = designation;
 }

}

There is a problem with this approach. Since Employee class provides setter methods, you can’t make the Employee class immutable.

Approach 2: We can make the class ‘Employee’ immutable, by not providing setter methods and providing different combinations of constructors to initialize instance properties.

Employee.java
public class Employee {
 private int id;
 private String firstName;
 private String lastName;
 private float salary;
 private String designation;

 private static final String DEFAULT_NAME = "no_name";
 private static final int DEFAULT_ID = -1;
 private static final String DEFAULT_DESIGNATION = "Engineer";
 private static final float DEFAULT_SALARY = 25000;

 public Employee() {
  this(DEFAULT_ID, DEFAULT_NAME, DEFAULT_NAME, DEFAULT_SALARY, DEFAULT_DESIGNATION);
 }

 public Employee(int id) {
  this(id, DEFAULT_NAME, DEFAULT_NAME, DEFAULT_SALARY, DEFAULT_DESIGNATION);
 }

 public Employee(int id, String firstName) {
  this(id, firstName, DEFAULT_NAME, DEFAULT_SALARY, DEFAULT_DESIGNATION);
 }

 public Employee(int id, String firstName, String lastName) {
  this(id, firstName, lastName, DEFAULT_SALARY, DEFAULT_DESIGNATION);
 }

 public Employee(int id, String firstName, String lastName, float salary, String designation) {
  this.id = id;
  this.firstName = firstName;
  this.lastName = lastName;
  this.salary = salary;
  this.designation = designation;
 }

 public int getId() {
  return id;
 }

 public String getFirstName() {
  return firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public float getSalary() {
  return salary;
 }

 public String getDesignation() {
  return designation;
 }

}

There is a problem with above model class, As you see the definition of Employee class, I provided 4 different constructors to initialize different combination of properties. What if my class has more instance properties, I will end up in more and more constructor definitions, which is not feasible. We can solve both the problems by providing builder object.

Approach 3: Define Employee model class using builder pattern.

Employee.java
public class Employee {
 private int id;
 private String firstName;
 private String lastName;
 private float salary;
 private String designation;

 private Employee() {

 }

 public int getId() {
  return id;
 }

 public String getFirstName() {
  return firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public float getSalary() {
  return salary;
 }

 public String getDesignation() {
  return designation;
 }

 public static class EmployeeBuilder {

  private static final String DEFAULT_NAME = "no_name";
  private static final int DEFAULT_ID = -1;
  private static final String DEFAULT_DESIGNATION = "Engineer";
  private static final float DEFAULT_SALARY = 25000;

  private int id;
  private String firstName;
  private String lastName;
  private float salary;
  private String designation;

  public EmployeeBuilder() {
   this.id = DEFAULT_ID;
   this.firstName = this.lastName = DEFAULT_NAME;
   this.salary = DEFAULT_SALARY;
   this.designation = DEFAULT_DESIGNATION;
  }

  public EmployeeBuilder id(int id) {
   this.id = id;
   return this;
  }

  public EmployeeBuilder firstName(String firstName) {
   this.firstName = firstName;
   return this;
  }

  public EmployeeBuilder lastName(String lastName) {
   this.lastName = lastName;
   return this;
  }

  public EmployeeBuilder salary(float salary) {
   this.salary = salary;
   return this;
  }

  public EmployeeBuilder designation(String designation) {
   this.designation = designation;
   return this;
  }

  public Employee build() {
   Employee emp = new Employee();
   emp.designation = this.designation;
   emp.firstName = this.firstName;
   emp.id = this.id;
   emp.lastName = lastName;
   emp.salary = salary;
   return emp;
  }
 }

 @Override
 public String toString() {
  StringBuilder builder = new StringBuilder();
  builder.append("Employee [id=").append(id).append(", firstName=").append(firstName).append(", lastName=")
    .append(lastName).append(", salary=").append(salary).append(", designation=").append(designation)
    .append("]");
  return builder.toString();
 }

}

Test.java
public class Test {

 public static void main(String args[]) {
  
  Employee.EmployeeBuilder builder = new Employee.EmployeeBuilder();
  
  Employee emp = builder.id(553)
       .firstName("Hari krishna")
       .lastName("Gurram")
       .designation("Software Engineer")
       .build();
  
  System.out.println(emp);
 }

}

Output
Employee [id=553, firstName=Hari krishna, lastName=Gurram, salary=25000.0, designation=Software Engineer]

In real world applications, it is always good coding practice to code for an interface. For example, you can define a Builder interface like below, and make the EmployeeBuilder class implements the Builder interface.

public interface Builder<T> {
 public T build();
}

Implement EmployeeBuilder like below.
public static class EmployeeBuilder implements Builder<Employee> {

  private static final String DEFAULT_NAME = "no_name";
  private static final int DEFAULT_ID = -1;
  private static final String DEFAULT_DESIGNATION = "Engineer";
  private static final float DEFAULT_SALARY = 25000;

  private int id;
  private String firstName;
  private String lastName;
  private float salary;
  private String designation;

  public EmployeeBuilder() {
   this.id = DEFAULT_ID;
   this.firstName = this.lastName = DEFAULT_NAME;
   this.salary = DEFAULT_SALARY;
   this.designation = DEFAULT_DESIGNATION;
  }

  public EmployeeBuilder id(int id) {
   this.id = id;
   return this;
  }

  public EmployeeBuilder firstName(String firstName) {
   this.firstName = firstName;
   return this;
  }

  public EmployeeBuilder lastName(String lastName) {
   this.lastName = lastName;
   return this;
  }

  public EmployeeBuilder salary(float salary) {
   this.salary = salary;
   return this;
  }

  public EmployeeBuilder designation(String designation) {
   this.designation = designation;
   return this;
  }

  @Override
  public Employee build() {
   Employee emp = new Employee();
   emp.designation = this.designation;
   emp.firstName = this.firstName;
   emp.id = this.id;
   emp.lastName = lastName;
   emp.salary = salary;
   return emp;
  }
 }



No comments:

Post a Comment