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

175 Views Asked by At

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");
2

There are 2 best solutions below

0
Shameem94 On BEST ANSWER

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);
}
0
Reilas On

"... What is the purpose of defining this class? ..."

The idea is to separate the field accessors, from the builder design.

It's a common misconception that accessors are created specifically for accessing fields from outside of the class.

They are actually just part of the object-oriented design principle; as a way to define a single point of assignment and retrieval, of the data.

In other words, setters and getters should be used throughout the class.

class Employee {
    int id;
    String name;

    public Employee(int id, String name) {
        setId(id);
        setName(name);
    }

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

    public void setName(String name) {
        this.name = name;
    }
}

As opposed to,

public Employee(int id, String name) {
    this.id = id;
    this.name = name;
}

This considered, you would want to have an inner-class, to separate the design.

Keep in mind though, design patterns are just outlines.  There is no exact way to write them; as they are just recommendations.

If adjusting the pattern, by removing the inner-class, improves your design, I would recommend using that.

Sometimes design concepts can clash, and actually degrade the scalability of the product, thus, defeating the purpose of their use.