Consider a builder when faced with many constructor parameters

Constructors with many parameters are hard to read. The only clue you have about what each parameter means is its order. It is not easily readable. Even more troubles emerge, when we have optional parameters. Then we start to drown in a sea of constructors with different set of parameters, a so called telescopic constructor pattern. We might even change the order of the parameters, to obtain different effects – this is very bad practice.

Let’s not follow this path… builders to the rescue! With builder pattern, we can write a more verbose code. We can design for optional parameters. We can get immutable objects, without the need to create a constructor to satisfy all final fields initialisation.

Let’s see a source code sample for builder pattern:

public class Tea {
    public enum TeaType {BLACK, GREEN, WHITE}
    private final TeaType teaType;
    public enum Extras {LEMON, HONEY, GINGER}
    private final Set extras;
    private final int waterMilliliters;
    private final int sugarGrams;
    private final int brewingTimeSeconds;

    public static class Builder{
        private final TeaType teaType;
        private final EnumSet extras = EnumSet.noneOf(Extras.class);
        private int waterMilliliters = 250;
        private int sugarGrams = 10;
        private int brewingTimeSeconds = 180;

        public Builder(TeaType teaType) {
            this.teaType = teaType;
        }

        public Builder withExtra(Extras extra) {
            extras.add(extra);
            return this;
        }

        public Builder waterMilliliters(int water) {
            this.waterMilliliters = water;
            return this;
        }

        public Builder sugarGrams(int sugar) {
            this.sugarGrams = sugar;
            return this;
        }

        public Builder brewingTimeSeconds(int brewing) {
            this.brewingTimeSeconds = brewing;
            return this;
        }

        //we would normally use build(), by convention
        public Tea brew() {
            return new Tea(this);
        }

    }

    private Tea(Builder builder) {
        teaType = builder.teaType;
        extras = builder.extras.clone();
        waterMilliliters = builder.waterMilliliters;
        sugarGrams = builder.sugarGrams;
        brewingTimeSeconds = builder.brewingTimeSeconds;
    }
}

An here is how we would call the builder:

Tea bigTeaWithLemonAndHoney = new Tea.Builder(BLACK).withExtra(LEMON).withExtra(HONEY).waterMilliliters(400).brew();

Several highlights to keep in mind when designing and using a builder:

  • Builder is an inner public static class
  • The build() method returns a complete, possibly immutable object – no intermediate states!
  • The built class needs a private constructor which takes builder as a parameter (that’s why it needs to be a nested class)
  • To invoke the builder, we use the new keyword

https://food.ndtv.com/recipe-honey-lemon-ginger-tea-335562

This post is based on item Consider a builder when faced with many constructor parameters from Joshua Bloch Effective Java Third Edition (get it on Amazon: https://www.amazon.com/Effective-Java-3rd-Joshua-Bloch/dp/0134685997)