mbmast
mbmast

Reputation: 1100

Java Builder Design Pattern Redundant Field Declaration in Class and its Builder

The classic Builder Pattern requires fields to be declared in the class-to-be-built and the exact same fields to be declared in the builder class. This can lead to problems when there are many fields and, during refactoring, the field types are not kept in sync. Here's an example of what I mean (I borrowed this code sample from an article by Joshua Block):

// Builder Pattern
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 {
        private int servingSize   = 0;
        private int servings      = 0;
        private int calories      = 0;
        private int fat           = 0;
        private int carbohydrate  = 0;
        private int sodium        = 0;

        public Builder servingSize(int val)
            { servingSize = val;   return this; }
        public Builder servings(int val)
            { servings = val;      return this; }
        public Builder calories(int val)
            { calories = val;      return this; }
        public Builder fat(int val)
            { fat = val;           return this; }
        public Builder carbohydrate(int val)
            { carbohydrate = val;  return this; }
        public Builder sodium(int val)
            { sodium = 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;
    }
}

Now let's assume that servingSize needs to be changed from int to long and that this change is done in NutritonFacts but, by accident, not also done in the static Builder.

Admittedly less problematic is the number of fields. NutritionFacts has 6 fields and, therefore, so does Builder. What if there were 20 or 100 fields? Duplicating them all in NutritionFacts and Builder would be real pain. Is there a better way so that all the duplication and potential for type-syncing errors can be avoided?

Upvotes: 4

Views: 1550

Answers (2)

Vijay Vankhede
Vijay Vankhede

Reputation: 3058

You can enhance classic builder pattern with the step builder pattern in order to build objects with a no brain interface, easy to use, impossible to get wrong. See this post for more detail.

Upvotes: 2

duckstep
duckstep

Reputation: 1138

You can use a NutritionFacts object to store your Builder's state:

// Builder Pattern
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 {
        private NutritionFacts state = new NutritionFacts(0,0,0,0,0,0);

        public Builder servingSize(int val) { 
            state = new NutritionFacts(val, state.servings, state.calories, state.fat, state.sodium, state.carbohydrate);
            return this;
        }
        [...]

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

    private NutritionFacts(Builder builder) {
        servingSize  = builder.state.servingSize;
        servings     = builder.state.servings;
        calories     = builder.state.calories;
        fat          = builder.state.fat;
        sodium       = builder.state.sodium;
        carbohydrate = builder.state.carbohydrate;
    }

    private NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize  = servingSize;
        this.servings     = servings;
        this.calories     = calories;
        this.fat          = fat;
        this.sodium       = sodium;
        this.carbohydrate = carbohydrate;
    }
}  

As NutritionFacts is immutable, this will require constructing a new state object for every change, which might or might not be worth it.

It will be easier if you can make NutritionFacts's internal state mutual, but use private setters - making the objects immutable by definition instead of by the final keyword:

// Builder Pattern
public class NutritionFacts {
    private int servingSize = 0;
    private int servings = 0;
    private int calories = 0;
    private int fat = 0;
    private int sodium = 0;
    private int carbohydrate = 0;

    public static class Builder {
        private NutritionFacts state = new NutritionFacts();

        public Builder servingSize(int val) { 
            state.servingSize = val;
            return this;
        }
        public Builder servings(int val) { 
            state.servings = val;
            return this;
        }
        [...]

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

    private NutritionFacts(Builder builder) {
        servingSize  = builder.state.servingSize;
        servings     = builder.state.servings;
        calories     = builder.state.calories;
        fat          = builder.state.fat;
        sodium       = builder.state.sodium;
        carbohydrate = builder.state.carbohydrate;
    }
}  

Upvotes: 3

Related Questions