The builder pattern (not the builder design pattern) in Java

Plan


Disclaimer :
Sure that we could code all of that with Lombok annotations. In simple cases, it is fine but if you need to add some customization you can be limited by Lombok. With your own code, you will never be.

Builder pattern, builder design pattern, fluent API. Are these the same thing ?

No there are not.
First : don’t mix the builder pattern/idiom with the GOF Builder design pattern : these have completely different goals. The first is an alternative to constructors/factory/setters to create an object with a consistent state for a specific class while the other one separates the logic/algorithmic for building a kind of object and the different implementations that may have these objects (more specifically these parts).

Second : builder pattern and fluent API are very close things but these have an important difference. The first one creates one object when the client terminated to describe the object to create while the fluent API doesn’t require that the client invokes a specific method to say that the object is created since generally fluent API relies on immutable class and so each methods invoked on the object that changes its state will return a new instance of the object to create.

When do we want to consider the builder pattern ?

The builder pattern makes sense in three main cases :
– constructor with a certain number of arguments, at least 4 generally (Java effective third edition : Item 2).
– you don’t have « many » arguments (for example you have 3 of them) but these have the same types and so it make your API error prone : the client may confuse the order of them.
– you have an immutable class and want to provide a fluent API to ease the client usage : both object creation and object change.

A simple builder

Suppose we have a Chinchilla class where we have to value 4 information (name, color, owner and weight) to set its initial state and we don’t want to use neither public constructor because this looks like quite clumsy and setter doesn’t guarantee, nor a setter approach that doesn’t guarantee that the object is correctly valued. Defining a builder is rather simple.
Main points to focus on are :
– defining the builder as a nested class of the class to create and make the class to create with a private constructor. These are not optional at all as these guarantee that the class for which we want to use the builder can only be instantiated from the builder.  
– the builder class has to be a nested static class and not just a nested class : we don’t want to create a Chinchilla to instantiate the Builder, that is in fact the reverse.
– the builder and the class has some duplication concerning fields to value. 
– the builder API is fluent.

public class Chinchilla {
 
    public enum Color {
        GRAY, WHITE;
    }
 
    public static class Builder {
 
        private String name;
        private Color color;
        private Owner owner;
        private float weight;
 
        public Builder withName(String name) {
            this.name = name;
            return this;
        }
 
        public Builder withColor(Color color) {
            this.color = color;
            return this;
        }
 
        public Builder withOwner(Owner owner) {
            this.owner = owner;
            return this;
        }
 
        public Builder withWeight(float weight) {
            this.weight = weight;
            return this;
        }
 
        public Chinchilla build() {
            return new Chinchilla(this);
        }
 
    }
 
    private String name;
    private Color color;
    private Owner owner;
    private float weight;
 
 
    private Chinchilla(Builder builder) {
        name = builder.name;
        color = builder.color;
        owner = builder.owner;
        weight = builder.weight;
    }
 
    public String getName() {
        return name;
    }
 
    public Color getColor() {
        return color;
    }
 
    public Owner getOwner() {
        return owner;
    }
 
    public float getWeight() {
        return weight;
    }
 
    @Override
    public String toString() {
        return "Chinchilla{" +
                "name='" + name + '\'' +
                ", color=" + color +
                ", owner=" + owner +
                ", weight=" + weight +
                '}';
    }

The same builder with consistency checks

Giving the ability to instantiate a chinchilla with no name, no color, no weight makes no sense.  So we want to add some consistency checks before effectively creating the chinchilla instance.
Main points to focus on are :
– defining the consistency rules in the builder is better : fail fast and stacktrace clearer for the client that manipulates the builder and never the constructor of the class that relies on it.
– in the sample code I make manual checks, it is for make it « plain ». In some cases,  consider the Java API for bean validation(JSR 380) as a fair alternative.

public class Chinchilla {
 
    public enum Color {
        GRAY, WHITE;
    }
 
    public static class Builder {
 
        private String name;
        private Color color;
        private Owner owner;
        private float weight;
 
        public Builder withName(String name) {
            this.name = name;
            return this;
        }
 
        public Builder withColor(Color color) {
            this.color = color;
            return this;
        }
 
        public Builder withOwner(Owner owner) {
            this.owner = owner;
            return this;
        }
 
        public Builder withWeight(float weight) {
            this.weight = weight;
            return this;
        }
 
        public Chinchilla build() {
            StringBuilder errorMsg = new StringBuilder();
            if (name == null || name.equals("")) {
                errorMsg.append("name has to be not null or empty, ");
            }
            if (color == null) {
                errorMsg.append("color has to be not null, ");
            }
 
            if (weight <= 0) {
                errorMsg.append("weigth has to be > to 0, ");
            }
 
            if (errorMsg.length() != 0) {
                errorMsg.deleteCharAt(errorMsg.length() - 2);
                throw new IllegalArgumentException(
                        "Chinchilla cannot be created because : " + errorMsg.toString());
            }
            return new Chinchilla(this);
        }
 
    }
 
    private String name;
    private Color color;
    private Owner owner;
    private float weight;
 
 
    private Chinchilla(Builder builder) {
        name = builder.name;
        color = builder.color;
        owner = builder.owner;
        weight = builder.weight;
    }
 
    public String getName() {
        return name;
    }
 
    public Color getColor() {
        return color;
    }
 
    public Owner getOwner() {
        return owner;
    }
 
    public float getWeight() {
        return weight;
    }
 
    @Override
    public String toString() {
        return "Chinchilla{" +
                "name='" + name + '\'' +
                ", color=" + color +
                ", owner=" + owner +
                ", weight=" + weight +
                '}';
    }
 
 
}

Now if we use the builder in this way :

Chinchilla build = new Chinchilla.Builder().build();

We will get the expected exception :

Exception in thread "main" java.lang.IllegalArgumentException: Chinchilla cannot be created because : name has to be not null or empty, color has to be not null, weigth has to be > to 0 
	at ...

Builder with inheritance

The Java effective third edition : Item 2 says « the builder pattern is well suited to class hierarchies » and of course it is right.
Suppose now that we would like to define an abstract animal class and multiple subclasses of them where we want to use the builder for each case. 

Main points to focus on are : 
– allowing fluent invocations from any public methods of any builder class of the current hierarchy of the builder class currently used by the client. For that, we need to specify a generic type with a recursive type parameter (Item 30) for the builder classes combined to the simulated self-type idiom.
– each subclass has to define its way to build the instance that it handles, so an abstract Animal build() is defined in the parent class.
– but defining a  Animal generic parameterized type in the Builder classes to get for example a T build() method is helpless because from the client side we don’t need to declare the builder variable as a generic. Indeed, clients that use the builder don’t use it by its interface but its concrete builder class that is not generic.
For that reason, build() implemented in the subclasses have to specify the specific return type in the method declaration. We can do it with the covariance return allowed from Java 5.

Here is the parent class : 

public abstract class Animal {
 
    private String name;
 
    public Animal(Builder<?> builder) {
        this.name = builder.name;
    }
 
    public String getName() {
        return name;
    }
 
    public abstract static class Builder<T extends Builder<T>> {
 
        private String name;
 
        public T withName(String name) {
            this.name = name;
            return self();
        }
 
        public abstract T self() ;
        public abstract Animal build();
 
        public String getName() {
            return name;
        }
    }
}

And here is a subclass :

public class Chinchilla extends Animal {
 
    public enum Color {
        GRAY, WHITE;
    }
 
    public static class Builder extends Animal.Builder<Builder> {
 
        private Color color;
        private Owner owner;
        private float weight;
 
 
        public Builder withColor(Color color) {
            this.color = color;
            return this;
        }
 
        public Builder withOwner(Owner owner) {
            this.owner = owner;
            return this;
        }
 
        public Builder withWeight(float weight) {
            this.weight = weight;
            return this;
        }
 
 
        @Override
        public Chinchilla build() {
            return new Chinchilla(this);
        }
 
        @Override
        public Builder self() {
            return this;
        }
    }
 
    private Color color;
    private Owner owner;
    private float weight;
 
    private Chinchilla(Builder builder) {
        super(builder);
        color = builder.color;
        owner = builder.owner;
        weight = builder.weight;
    }
 
    public Color getColor() {
        return color;
    }
 
    public Owner getOwner() {
        return owner;
    }
 
    public float getWeight() {
        return weight;
    }
 
    @Override
    public String toString() {
        return "Chinchilla{" +
                "name=" + getName() +
                "color=" + color +
                ", owner=" + owner +
                ", weight=" + weight +
                '}';
    }
}

We could use the concrete builder in this way :

        Chinchilla chinchilla =
                new Chinchilla.Builder().withColor(Chinchilla.Color.GRAY)
                                        .withName("scoubi")
                                        .withWeight(50)
                                        .withOwner(new Owner("David"))
                                        .build();
Ce contenu a été publié dans Non classé. Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *