The Builder pattern

The Builder pattern helps us create complex objects that can have different representations, simplifying the process and (optionally) preserving immutability – encapsulating the code responsible for assembling an object in a separate Builder class.

In simpler terms, it delegates the responsibility of creating a complex object to its Builder, whose job it is to make sure that the object is never created in an invalid state.

The pattern, while having a deceivingly simple name, has some less than obvious implications. It can get even more complicated, as there is more than one way of implementing it. However, in this post I will try to show the most common implementations, along with some additional explanation.

Usage example – basic

Suppose you have a Spaceship class. It has a name, health, number of available rounds of ammo and information on whether or not it is a friendly ship. A Spaceship can describe() itself.

public class Spaceship {
    private String name;
    private int health;
    private int ammo;
    private boolean friendly;

    private Spaceship(String name, int health) {
        this.name = name;
        this.health = health;
    }

    public String describe() {
        StringBuilder sb = new StringBuilder();
        sb.append("This spaceship is called ")
                .append(name)
                .append(", it is ");

        if(!friendly) {
            sb.append(" not ");
        }

        sb.append("friendly. Its health is ")
                .append(health)
                .append(" and it has ")
                .append(ammo)
                .append(" round(s) of ammunition.");

        return sb.toString();
    }
}

Notice that the constructor of Spaceship is private. This ensures that our spaceships can only be constructed via our Builder class (which we are about to create).

Here are some requirements that we might have for our Spaceship instances:

  • We want to create many different spaceships – some of them friendly, some of them not. Some of them armed to the teeth, some of them unarmed.
  • We want to make sure a spaceship is not created with a negative health value or a negative ammo value.
  • Furthermore, we want to make sure that a friendly ship is always unarmed (has an ammo value of 0).

First, we create a Builder class inside of our Spaceship class:

public class Spaceship {

    [...]

    public static class Builder {
        private Spaceship spaceshipToBuild;

        Builder(String name) {
            spaceshipToBuild = new Spaceship(name, 100);
        }

        public Builder setHealth(int health) {
            if (health < 1) {
                throw new IllegalArgumentException("Health cannot be less than 1");
            }
            spaceshipToBuild.health = health;
            return this;
        }

        public Builder setAmmo(int ammo) {
            if (ammo < 0) {
                throw new IllegalArgumentException("Ammo cannot be less than 0");
            }
            spaceshipToBuild.ammo = ammo;
            return this;
        }

        public Builder setFriendly(boolean friendly) {
            spaceshipToBuild.friendly = friendly;
            return this;
        }
    }
}

The Builder class is the core of the Builder pattern. It could be implemented either inside, or outside of the Spaceship class, but doing it inside lets us keep Spaceship's constructor private and access its private fields directly, rather than through getter and setter methods.

The Builder class contains one field, a single instance of Spaceship. It also contains a constructor with required fields (in this case – name) and setters for all optional fields of Spaceship (healthammofriendly).

Builder's constructor initializes spaceshipToBuild using Spaceship's private constructor – we pass the required name value and a default value for health (100). This way, spaceshipToBuild is initialized in a valid state: it has a name, a positive health value, an ammo value of 0 and a friendly value of false.

We can then mutate the spaceshipToBuild object using Builder's setters. These setters also have optional validation on them. In this case, we validate if the health and ammo values are within valid ranges and throw an IllegalArgumentException if one of them isn’t.

Furthermore, the setters use the fluent interface pattern, so that we can chain the methods together later.

But how to get to the object once it is in a state that we like? We must add a build() method to our Builder:

public class Spaceship {

    [...]

    public static class Builder {

        [...]

        public Spaceship build() {
            if(spaceshipToBuild.ammo > 1 && spaceshipToBuild.friendly) {
                throw new IllegalStateException("A friendly ship cannot be armed!");
            }

            return spaceshipToBuild;
        }
    }
}

In this method, we can perform state checks on our object (in this case: we make sure the ship is not both armed and friendly) and return it.

Now we can finally create some spaceships:

public class BuilderPatternBasicExample {

    public static void main(String[] args) {
        Spaceship roci = new Spaceship.Builder("Roci").build();
        System.out.println(roci.describe());

        Spaceship nostromo = new Spaceship.Builder("Nostromo")
                .setHealth(75)
                .setAmmo(500)
                .build();
        System.out.println(nostromo.describe());

        Spaceship mrFriendly = new Spaceship.Builder("Mr Friendly")
                .setFriendly(true)
                .build();
        System.out.println(mrFriendly.describe());
    }
}

Which should give us the following output:

This spaceship is called Roci, it is not friendly. Its health is 100 and it has 0 round(s) of ammunition.
This spaceship is called Nostromo, it is not friendly. Its health is 75 and it has 500 round(s) of ammunition.
This spaceship is called Mr Friendly, it is friendly. Its health is 100 and it has 0 round(s) of ammunition.

The final code is:

public class BuilderPatternBasicExample {

    public static void main(String[] args) {
        Spaceship roci = new Spaceship.Builder("Roci").build();
        System.out.println(roci.describe());

        Spaceship nostromo = new Spaceship.Builder("Nostromo")
                .setHealth(75)
                .setAmmo(500)
                .build();
        System.out.println(nostromo.describe());

        Spaceship mrFriendly = new Spaceship.Builder("Mr Friendly")
                .setFriendly(true)
                .build();
        System.out.println(mrFriendly.describe());
    }
}

public class Spaceship {
    private String name;
    private int health;
    private int ammo;
    private boolean friendly;

    private Spaceship(String name, int health) {
        this.name = name;
        this.health = health;
    }

    public String describe() {
        StringBuilder sb = new StringBuilder();
        sb.append("This spaceship is called ")
                .append(name)
                .append(", it is ");

        if(!friendly) {
            sb.append(" not ");
        }

        sb.append("friendly. Its health is ")
                .append(health)
                .append(" and it has ")
                .append(ammo)
                .append(" round(s) of ammunition.");

        return sb.toString();
    }

    public static class Builder {
        private Spaceship spaceshipToBuild;

        Builder(String name) {
            spaceshipToBuild = new Spaceship(name, 100);
        }

        public Builder setHealth(int health) {
            if (health < 1) {
                throw new IllegalArgumentException("Health cannot be less than 1");
            }
            spaceshipToBuild.health = health;
            return this;
        }

        public Builder setAmmo(int ammo) {
            if (ammo < 0) {
                throw new IllegalArgumentException("Ammo cannot be less than 0");
            }
            spaceshipToBuild.ammo = ammo;
            return this;
        }

        public Builder setFriendly(boolean friendly) {
            spaceshipToBuild.friendly = friendly;
            return this;
        }

        public Spaceship build() {
            if(spaceshipToBuild.ammo > 1 && spaceshipToBuild.friendly) {
                throw new IllegalStateException("A friendly ship cannot be armed!");
            }

            return spaceshipToBuild;
        }
    }
}

Usage example – another implementation (supports final fields)

Assume that we have a Planet class. We identify the Planet by its name, but it also has a radius , information on whether or not it is habitableand its population count. A Planet can describe() itself.

public class Planet {
    private final String name;
    private final boolean habitable;
    private final int population; //in millions of people
    private final int radius; //in millions of km

    public String describe() {
        StringBuilder sb = new StringBuilder();
        sb.append("This planet is called ")
                .append(name)
                .append(", it is ");

        if(!habitable) {
            sb.append("not ");
        }

        sb.append("habitable. Its population is ")
                .append(population)
                .append(" and its radius is ")
                .append(radius)
                .append(".");

        return sb.toString();
    }
}

An optional requirement that we’re making here is that the planet is immutable. That means that its fields cannot be changed (mutated) after our Planet is instantiated. This is not necessary to implement the Builder pattern, but will allow me to show its true power.

If we tried to compile the code now, it would not work – since I have declared the fields of Planet as final (again, this is optional for the pattern), we must now initialize them.

First, we create the Builder class within our Planet class:

public class Planet {

    [...]

    public static class Builder {
        private String name;
        private boolean habitable;
        private int population;
        private int radius;

        public Builder(String name) {
            this.name = name;
            this.habitable = false;
            this.population = 0;
            this.radius = 10;
        }

        public Builder setHabitable(boolean habitable) {
            this.habitable = habitable;
            return this;
        }

        public Builder setPopulation(int population) {
            if (population < 0) {
                throw new IllegalArgumentException("Popoulation cannot be negative");
            }
            this.population = population;
            return this;
        }

        public Builder setRadius(int radius) {
            if (radius < 0) {
                throw new IllegalArgumentException("Radius cannot be negative");
            }
            this.radius = radius;
            return this;
        }
    }
}

Notice, that our Builderhas the same fields as Planet (this is necessary because they are final in Planet), and contains all the setters that Planetis missing. It also contains a one-parameter constructor, in which name is provided and optional parameters are set to their default values.

I have decided to add some restrictions on my Planets – their radii and populations cannot be negative. This is optional, but I wanted to show one way how you can validate arguments passed to the Builder.

The Builder instance acts as an intermediate object – one which we can mutate to our heart’s content – you could say it’s like a baby in the womb: it’s being formed to become the object we want (in this case: a Planet), but it isn’t quite there yet.

How to turn our Builder instance into a fully fledged Planet? With a build() function, of course!

public class Planet {

    [...]

    private Planet(Builder builder) {
        name = builder.name;
        habitable = builder.habitable;
        population = builder.population;
        radius = builder.radius;
    }

    public static class Builder {

        [...]

        public Planet build() {
            Planet Planet = new Planet(this);

            if(!planet.habitable && planet.population > 0) {
                throw new IllegalStateException("An unhabitable planet can't have a non-zero population!");
            }

            return Planet;
        }
    }
}

Here, we create a new instance of Planet by invoking a Planet constructor that takes a Builder instance (our baby-in-womb) as a parameter.
Since we have written the Builder class inside of Planet, we can access Builder's private fields in Planet's constructor. In it, all we do is get the values of our Builder's fields and apply them to the Planet instance. We can also make that constructor private, further encapsulating our object’s creation logic.

I want to make sure that an uninhabitable planet won’t have a non-zero population. This is also optional to the Builder pattern, but shows one of its strengths – I can validate the object’s state in my build() method, before returning it. In this case, I return an IllegalStateException if the radius is negative, as the Planet object that we are being asked to return is in an invalid state.

Now that we have a Planet class and its Builder, let’s create some planets!

public class BuilderPatternExample {

    public static void main(String[] args) {
        Planet alphaPrime = new Planet.Builder("Alpha Prime").build();
        System.out.println(alphaPrime.describe());

        Planet delta3 = new Planet.Builder("Delta 3")
                .setHabitable(true)
                .setPopulation(10000)
                .setRadius(3500)
                .build();
        System.out.println(delta3.describe());
    }
}

The above code should output:

This planet is called Alpha Prime, it is not habitable. Its population is 0 and its radius is 10.
This planet is called Delta 3, it is habitable. Its population is 10000 and its radius is 3500.

The complete example is below:

public class BuilderPatternExample {

    public static void main(String[] args) {
        Planet alphaPrime = new Planet.Builder("Alpha Prime").build();
        System.out.println(alphaPrime.describe());

        Planet delta3 = new Planet.Builder("Delta 3")
                .setHabitable(true)
                .setPopulation(10000)
                .setRadius(3500)
                .build();
        System.out.println(delta3.describe());
    }
}

public class Planet {
    private final String name;
    private final boolean habitable;
    private final int population; //in millions of people
    private final int radius; //in millions of km

    private Planet(Builder builder) {
        name = builder.name;
        habitable = builder.habitable;
        population = builder.population;
        radius = builder.radius;
    }

    public String describe() {
        StringBuilder sb = new StringBuilder();
        sb.append("This planet is called ")
                .append(name)
                .append(", it is ");

        if (!habitable) {
            sb.append("not ");
        }

        sb.append("habitable. Its population is ")
                .append(population)
                .append(" and its radius is ")
                .append(radius)
                .append(".");

        return sb.toString();
    }

    public static class Builder {
        private String name;
        private boolean habitable;
        private int population;
        private int radius;

        public Builder(String name) {
            this.name = name;
            this.habitable = false;
            this.population = 0;
            this.radius = 10;
        }

        public Builder setHabitable(boolean habitable) {
            this.habitable = habitable;
            return this;
        }

        public Builder setPopulation(int population) {
            if (population < 0) {
                throw new IllegalArgumentException("Popoulation cannot be negative");
            }

            this.population = population;
            return this;
        }

        public Builder setRadius(int radius) {
            if (radius < 0) {
                throw new IllegalArgumentException("Radius cannot be negative");
            }

            this.radius = radius;
            return this;
        }

        public Planet build() {
            Planet planet = new Planet(this);

            if(!planet.habitable && planet.population > 0) {
                throw new IllegalStateException("An unhabitable planet can't have a non-zero population!");
            }

            return planet;
        }
    }
}

Conclusion

You may see slightly different implementations of the Builder pattern in the wild, but you should now understand the basics of what it is about.

Which of them to use is up to you and depends on the circumstances. The first implementation shown is definitely easier to implement, while the second allows for a higher level of immutability in objects. The second one, however, demands that you create a separate constructor dedicated solely for the Builder, which could be viewed as a con.

It is, however, undeniable that the Builder pattern is a useful tool to have in one’s arsenal.

Daniel Frąk Written by:

Be First to Comment

    Leave a Reply

    Your email address will not be published. Required fields are marked *