javadesign-patternsbuilder-pattern

Builder pattern without inner class


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 +
            '}';
}
}

Solution

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