Search code examples
javadesign-patternsbuilder

Why is the usage of builder pattern highly encouraged instead of a straight forward implementation of fluent API in Java?


Consider the following example of builder pattern:

import java.util.Objects;

public class Vehicle {
    private final String brand;
    private final int wheels;
    private final int doors;
    private final int maxSpeed;

    private Vehicle(Builder builder) {
        this.brand = Objects.requireNonNull(builder.brand, "brand");
        this.wheels = Objects.requireNonNull(builder.wheels, "wheels");
        this.doors = Objects.requireNonNull(builder.doors, "doors");
        this.maxSpeed = Objects.requireNonNull(builder.maxSpeed, "maxSpeed");
    }

    public static Builder builder() {
        return new Builder();
    }

    public void display() {
        System.out.println("brand = " + getBrand());
        System.out.println("wheels = " + getWheels());
        System.out.println("doors = " + getDoors());
        System.out.println("maxSpeed = " + getMaxSpeed());
    }

    public String getBrand() {
        return brand;
    }

    public int getWheels() {
        return wheels;
    }

    public int getDoors() {
        return doors;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public static class Builder {
        private String brand;
        private Integer wheels;
        private Integer doors;
        private Integer maxSpeed;

        Builder() {
        }

        public Builder setBrand(String brand) {
            this.brand = brand;
            return this;
        }

        public Builder setWheels(int wheels) {
            this.wheels = wheels;
            return this;
        }

        public Builder setDoors(int doors) {
            this.doors = doors;
            return this;
        }

        public Builder setMaxSpeed(int maxSpeed) {
            this.maxSpeed = maxSpeed;
            return this;
        }

        public Builder of(Vehicle vehicle) {
            this.brand = vehicle.brand;
            this.wheels = vehicle.wheels;
            this.doors = vehicle.doors;
            this.maxSpeed = vehicle.maxSpeed;
            return this;
        }

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

When we create an object of Vehicle type, we have:

Vehicle vehicle = new Vehicle.Builder()
                .setBrand("Mercedes")
                .setWheels(4)
                .setDoors(4)
                .setMaxSpeed(250)
                .build();

On the other hand, one can simply return this in each setter of the class Vehicle, e.g.:

public class Vehicle {
    private final String brand;
    private final int wheels;
    private final int doors;
    private final int maxSpeed;

    private Vehicle(String brand, int wheels, int doors, int maxSpeed){
        this.brand = brand;
        this.wheels = wheels;
        this.doors = doors;
        this.maxSpeed = maxSpeed;
    }

    public Vehicle() {
    }

    public String getBrand() {
        return brand;
    }

    public int getWheels() {
        return wheels;
    }

    public int getDoors() {
        return doors;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public Vehicle setBrand(String brand) {
        this.brand = brand;
        return this;
    }

    public Vehicle setWheels(int wheels) {
        this.wheels = wheels;
        return this;
    }

    public Vehicle setDoors(int doors) {
        this.doors = doors;
        return this;
    }

    public Vehicle setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
        return this;
    }

    public void display() {
        System.out.println("brand = " + getBrand());
        System.out.println("wheels = " + getWheels());
        System.out.println("doors = " + getDoors());
        System.out.println("maxSpeed = " + getMaxSpeed());
    }
}

We can create an object of Vehicle type by:

Vehicle vehicle = new Vehicle();
vehicle.setBrand("Mercedes")
       .setDoors(4)
       .setMaxSpeed(250);

In my opinion, the second approach produces less code than the conventional builder pattern. Nevertheless, I know many libraries making use of builder pattern instead. So, what are the pros of builder pattern comparing to the second approach, which is literally less complicated?


Solution

  • Some benefits builder pattern compared with fluent api:

    1. Builder can return not only Vehicle class but SomeClass extends Vehicle
    2. You can add custom complex logic into build method (validation, switch implementation etc)
    3. You can't create Vehicle without necessary properties

    But it's absolutely normal use your approach if you dont need this benefits. Some libraries use that