Search code examples
javaeffective-javabuilder-pattern

Builder Pattern: which variant is preferred?


I was going through Effective Java book , and creating notes for my future reference , i came across Builder Pattern.

Well i understood what it is and how its suppose to be used.In the process i created a two example variations of the builder pattern.

I would need help in listing down the differences and the advantage each has? Well i certainly noticed that , Example 1 exposes less methods , there by less restrictive and more generic , there by allowing it to be used more flexibly.

Please point out other things i have missed?

Example 1

package item2;

/**
 * @author Sudhakar Duraiswamy
 *
 */
public  class Vehicle {

    private String type;
    private int wheels;

    interface Builder<T>{
        public  T build();
    }

    public static class CarBuilder implements Builder<Vehicle>{
        private String type;
        private int wheels;     

        CarBuilder createVehicle(){
            this.type= "Car";
            return this;
        }

        CarBuilder addWheels(int wheels){
            this.wheels = wheels;
            return this;
        }

        public Vehicle build(){
            Vehicle v = new Vehicle();
            v.type = type;
            v.wheels = wheels;
            return v;
        }               
    }

    public static class TruckBuilder implements Builder<Vehicle>{       
        private String type;
        private int wheels; 

        TruckBuilder createVehicle(){           
            this.type= "Truck";
            return this;
        }

        TruckBuilder addWheels(int wheels){
            this.wheels = wheels;
            return this;
        }

        public Vehicle build(){
            Vehicle v = new Vehicle();
            v.type = type;
            v.wheels = wheels;
            return v;
        }
    }   

    public Vehicle(){

    }

    public static void main(String[] args) {
        //This builds a car with 4 wheels
        Vehicle car = new Vehicle.CarBuilder().createVehicle().addWheels(4).build();

        //THis builds a Truck with 10 wheels
        Vehicle truck = new Vehicle.TruckBuilder().createVehicle().addWheels(10).build();

    }
}

Example 2

package item2;
/**
 * @author Sudhakar Duraiswamy
 *
 */
public  class Vehicle2 {

    private String type;
    private int wheels;

    interface Builder<T>{
        public  T build();      
        public String getType();
        public int getWheels() ;
    }

    public static class CarBuilder implements Builder<Vehicle2>{
        private String type;
        private int wheels;     

        public String getType() {
            return type;
        }
        public int getWheels() {
            return wheels;
        }

        CarBuilder createVehicle(){
            this.type= "Car";
            return this;
        }

        CarBuilder addWheels(int wheels){
            this.wheels = wheels;
            return this;
        }

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

    public static class TruckBuilder implements Builder<Vehicle2>{      
        private String type;
        private int wheels; 

        public String getType() {
            return type;
        }

        public int getWheels() {
            return wheels;
        }

        TruckBuilder createVehicle(){           
            this.type= "Truck";
            return this;
        }

        TruckBuilder addWheels(int wheels){
            this.wheels = wheels;
            return this;
        }

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


public Vehicle2(Builder<? extends Vehicle2> builder){
    Vehicle2 v = new Vehicle2();
    v.type = builder.getType();
    v.wheels = builder.getWheels();
}

    public Vehicle2(){
    }

    public static void main(String[] args) {            
        //This builds a car with 4 wheels
        Vehicle2 car = new Vehicle2.CarBuilder().createVehicle().addWheels(4).build();

        //THis builds a Truck with 10 wheels
        Vehicle2 truck = new Vehicle2.TruckBuilder().createVehicle().addWheels(10).build();
    }
}

Solution

  • None of the above.

    The first one doesn't allow building an immutable Vehicle, which is often why the Builder pattern is used.

    The second example is a variation of the first one which allows getting information from the builder using additional getter methods. But those those methods aren't used anywhere, except in the Vehicle constructor, which has access to the builder fields directly. I don't see the point in adding them.

    I see two more important things to improve:

    1. The two builder types do exactly the same thing. There's no need for two types. A single one is sufficient.
    2. What the createVehicle() method does should be done by the builder constructor. If you construct a CarBuilder, it's obviously to build a car, so the type of the vehicle should be set as soon as the builder is constructed. Here's how I would write it:

    .

    public final class Vehicle {
    
        private final String type;
        private final int wheels;
    
        private Vehicle(Builder builder) {
            this.type = builder.type;
            this.wheels = builder.wheels;
        }
    
        public static Builder carBuilder() {
            return new Builder("car");
        }
    
        public static Builder truckBuilder() {
            return new Builder("truck");
        }
    
        public static class Builder {
            private final String type;
            private int wheels;
    
            private Builder(String type) {
                this.type = type;
            }
    
            public Builder addWheels(int wheels){
                this.wheels = wheels;
                return this;
            }
    
            public Vehicle build() {
                return new Vehicle(this);
            }               
        }
    
        public static void main(String[] args) {
            Vehicle car = Vehicle.carBuilder().addWheels(4).build();
            Vehicle truck = Vehicle.truckBuilder().addWheels(10).build();
        }
    }