2

Lets say I have this builder pattern. I searched everywhere but couldn't find why would I need to use inner class if outer class has public consturctor.

public class User {

private final String firstName;
private final String surname;
private final int age;


public User(UserBuilder userBuilder) {
    this.firstName = userBuilder.firstName;
    this.surname = userBuilder.surname;
    this.age = userBuilder.age;
}



public static class UserBuilder {

    private final String firstName;
    private final String surname;
    private int age;

    public UserBuilder(String firstName, String surname) {
        this.firstName = firstName;
        this.surname = surname;
    }

    public UserBuilder age(int age) {
        this.age = age;
        return this;
    }


    public User build() {
        User user = new User(this);
        return user;
    }
}
}

Here I could rewrite this code without using inner class as :

public class User {

private String firstName;
private String surname;
private int age;


public User(String firstName, String surname) {
 this.firstName = firstName;
 this.surname= surname;
}


public User age(int age) {
    this.age = age;
    return this;
}

}
}

When I read about builder pattern they say this pattern prevents big consturctor blocks. And they use inner with setters (with fluent pattern). I don't understand why we need to create inner class I could do the same thing it without using an inner class if my constructor is public. Here another example for more complex variables:

class NutritionFacts {
  private final int servingSize;
  private final int servings;
  private int calories;
  private int fat;
  private int sodium;
  private int carbohydrate;


    public NutritionFacts(int servingSize, int servings) {
        this.servingSize = servingSize;
        this.servings = servings;
    }
    public NutritionFacts calories(int val)
    { calories = val; return this; }
    public NutritionFacts fat(int val)
    { fat = val; return this; }
    public NutritionFacts sodium(int val)
    { sodium = val; return this; }
    public NutritionFacts carbohydrate(int val)
    { carbohydrate = val; return this; }
    
@Override
public String toString() {
    return "NutritionFacts{" +
            "servingSize=" + servingSize +
            ", servings=" + servings +
            ", calories=" + calories +
            ", fat=" + fat +
            ", sodium=" + sodium +
            ", carbohydrate=" + carbohydrate +
            '}';
}
}
David Soroko
  • 8,521
  • 2
  • 39
  • 51
iraquois
  • 37
  • 8
  • 4
    The point of putting the builder in an inner class, is that you can make the constructor private, so that the only way to create an instance of the class is by using the builder (and not by using the public constructor directly). If you make the constructor public, then there isn't a very good reason anymore why the builder must be an inner class. – Jesper May 31 '21 at 12:20
  • @Jesper I have a project that allows to configure the main object in mutliple ways. Instead of introducing a number of private constructors for each case, the configuration parameters are encapsulated in a parameter object created by the public builder that it also uses to create the actual object itself. The actual main classonly exposes a single constructor taking this parameter object instead of exposing a constructor for each configuration scenario. With proper modularization you could also expose only the builder to the public and do all the hard initialization work in an internal package – Roman Vottner May 31 '21 at 12:40
  • @RomanVottner true, there are multiple ways to implement builders, and there is not one way that's the "best" or "correct" way for all cases. My answer was to specifically explain that the main reason to implement a builder class as an inner class is so that you can make the constructor of the outer class private - but that's not the only way you can implement the builder pattern. – Jesper May 31 '21 at 12:51

1 Answers1

2

The builder pattern is not there to provide fluent style creation (although its a nice benefit), it is there to provide sane object instantiation when multiple optional parameters are present.

It makes no sense to have

public User someOptionalParam(String someOptionalParam) {
    this.someOptionalParam = someOptionalParam;
    return this;
}

because the instance of User you get is not fully constructed, it lack the non-optional firstName, surname and age.

The builder pattern was popularized in the Gang of Four book (Design Patterns: Elements of Reusable Object-Oriented Software) in a way that is not language specific. Implementing the builder as an inner class has several other benefits in the context of the Java language.

Effective Java presents a builder pattern example:


public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        // Required parameters
        private final int servingSize;
        private final int servings;

        // Optional parameters - initialized to default values
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;
    
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }
        public Builder calories(int val)
            { calories = val; return this; }
        public Builder fat(int val)
            { fat = val; return this; }
        public Builder sodium(int val)
            { sodium = val; return this; }
        public Builder carbohydrate(int val)
            { carbohydrate = val; return this; }
        
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }
    
    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

Java lacks some language feature that make the pattern useful. Kotlin has named and optional parameters that allow the example above to be written as:


class NutritionFacts(
    val servingSize : Int,
    val servings:     Int,
    val calories:     Int = 0;
    val fat           Int = 0,
    val sodium        Int = 0, 
    val carbohydrate  Int = 0
)

val aFact = NutritionFacts(servingSize = 10, servings = 2, fat = 7)

Edit: Kudos to you for going through the exercise and re implementing NutritionFacts in your style.

The issue with your implementation is that it is mutable, and the only reason it is mutable is because of the way you implement the "building" part. There is nothing in this particular class itself that requires mutability.

NutritionFacts aFact = new NutritionFacts(1,2).calories(7).sodium(3);

// ....  

aFact.sodium(1).carbohydrate(9);

Perhaps it looks like I am moving the goal posts with regards to what the builder pattern is supposed to achieve in general but I alluded to this when I said that "builder as an inner class has several other benefits in the context of the Java language."

David Soroko
  • 8,521
  • 2
  • 39
  • 51
  • Sorry if I didn't understand but in my case I still don't see any difference. Both code works in the same way. I see what you mean but I could write non-optional parameters in outer constructor also. Still don't know why would I need an inner class. Actually it is better to edit thanks. – iraquois May 31 '21 at 14:29
  • Your example is simple enough to not need a builder at all, just a single constructor with three parameters. Try a more complex scenario e.g. `NutritionFacts` above. Note that both example of `NutritionFacts` create valid instances - the required instances variables are guaranteed to be initialised by the user. – David Soroko May 31 '21 at 14:31
  • 1
    Thank you for your explanation and your time. Here I can rewrite your code without using builder inner class and it will do the same thing. That is the part I didn't understand let me add an example – iraquois May 31 '21 at 19:09