Search code examples
javadesign-patternsbuilderbuilder-pattern

Why do we need a 'Builder class' in Builder design pattern?


In Builder design pattern a 'Builder' inner class is used through which we set the values for the fields of that class. What is the purpose of defining this class?

One of the main reasons cited for using a builder pattern is that it solves the Telescoping Constructor Problem

If so, why can't we just go ahead with setter methods?

(Note: Although there are some questions that had already discussed this topic (link) those questions and answers were not very straight forward. Hence, I had to draft this question)

For e.g., Why do this?

public class Employee {
  private int id;
  private String name;

  public Employee (Builder builder) {
    this.id = builder.id;
    this.name = builder.name;
  }

  public static class Builder {
    private int id;
    private String name;

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

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

    public Employee build() {
      return new Employee(this);
    }
  }
}

// Employee emp = new Employee.Builder().id(1).name("Abc").build();

instead of this?

public class Employee {
  private int id;
  private String name;

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

  public Employee name(String name) {
    this.name = name;
    return this;
  }
}

// Employee emp = new Employee().id(1).name("Abc");

Solution

  • Thanks for all the responses. As Elliott Frisch and others mentioned in the above comments, using a Builder inner class helps us achieve consistency and immutability (apart from preventing telescoping constructor anti-pattern). Let me elaborate it.

    If we use setter methods instead of Builder inner class we have the following limitations

    1. Class cannot be made immutable. For e.g., the following is possible

      Employee emp = new Employee().id(1).name("Abc");
      emp.name("Xyz");
      
    2. Fields are set after instantiation i.e., the object is first created followed by setting of each and every field. This can lead to inconsistent state of the object if it is shared by multiple threads. In other words, the object becomes accessible by other threads before all the fields are set.

    The above problems can be avoided if we use a Builder inner class (which is what Builder design pattern actually is).

    In addition to the above points we can also perform validations for fields inside the 'build()' method. For e.g.,

    public Employee build() {
      if (this.id == null) {
        throw new RuntimeException("'id' cannot be null");
      }
      return new Employee(this);
    }