2

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");
Shameem94
  • 31
  • 7
  • 3
    `Builder` is a *creational* design pattern. Your alternative is a fluent interface. They aren't the same thing. One reason to use a `Builder` is to ensure the `Employee` is never in an inconsistent state, and you would typically make the fields immutable as well. Why? Because then every `Employee` can be used by multiple threads safely. – Elliott Frisch Jul 04 '23 at 03:22
  • Does this answer your question? [When would you use the Builder Pattern?](https://stackoverflow.com/questions/328496/when-would-you-use-the-builder-pattern) – Maurice Perry Jul 04 '23 at 03:43
  • @MauricePerry Not exactly, that question is not very specific – Shameem94 Jul 04 '23 at 05:44
  • 1
    one use case: to have the `Employee` class immutable, that is, no methods to change the fields (`final` fields) – user16320675 Jul 04 '23 at 05:49
  • Actually, in modern Java, you should probably make the `Employee` a [*record*](https://docs.oracle.com/en/java/javase/20/language/records.html). – Elliott Frisch Jul 04 '23 at 13:05

2 Answers2

2

"... 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.

Reilas
  • 3,297
  • 2
  • 4
  • 17
1

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);
}
Shameem94
  • 31
  • 7