Design patterns Builder Pattern Builder Pattern / Java Implementation


Example

The Builder pattern allows you to create an instance of a class with many optional variables in an easy to read way.

Consider the following code:

public class Computer {

    public GraphicsCard graphicsCard;
    public Monitor[] monitors;
    public Processor processor;
    public Memory[] ram;
    //more class variables here...

    Computer(GraphicsCard g, Monitor[] m, Processer p, Memory ram) {
        //code omitted for brevity...
    }

    //class methods omitted...

}

This is all well and good if all of the parameters are necessary. What if there are a lot more variables and/or some of them are optional? You don't want to create a large number of constructors with each possible combination of required and optional parameters because it becomes difficult to maintain and for developers to understand. You also may not want to have a long list of parameters in which many may need to be entered as null by the user.

The Builder pattern creates an inner class called Builder that is used to instantiate only the desired optional variables. This is done through methods for each optional variable which take the variable type as a parameter and return a Builder object so that the methods can be chained with each other. Any required variables are put into the Builder constructor so that they can not be left out.

The Builder also includes a method called build() which returns the object that it is in and must be called at the end of the chain of method calls when building the object.

Following from the previous example, this code uses the Builder pattern for the Computer class.

public class Computer {

    private GraphicsCard graphicsCard;
    private Monitor[] monitors;
    private Processor processor;
    private Memory[] ram;
    //more class variables here...

    private Computer(Builder builder) {
        this.graphicsCard = builder.graphicsCard;
        this.monitors = builder.monitors;
        this.processor = builder.processor;
        this.ram = builder.ram;
    }

    public GraphicsCard getGraphicsCard() {
        return this.graphicsCard;
    }

    public Monitor[] getMonitors() {
        return this.monitors;
    }

    public Processor getProcessor() {
        return this.processor;
    }

    public Memory[] getRam() {
        return this.ram;
    }

    public static class Builder {
        private GraphicsCard graphicsCard;
        private Monitor[] monitors;
        private Processor processor;
        private Memory[] ram;

        public Builder(Processor p){
            this.processor = p;
        }

        public Builder graphicsCard(GraphicsCard g) {
            this.graphicsCard = g;
            return this;
        }

        public Builder monitors(Monitor[] mg) {
            this.monitors = mg;
            return this;
        }

        public Builder ram(Memory[] ram) {
            this.ram = ram;
            return this;
        }

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

An example of how this class would be used:

public class ComputerExample {

    public static void main(String[] args) {
        Computer headlessComputer = new Computer.Builder(new Processor("Intel-i3"))
                .graphicsCard(new GraphicsCard("GTX-960"))
                .build();

        Computer gamingPC = new Computer.Builder(new Processor("Intel-i7-quadcode"))
                .graphicsCard(new GraphicsCard("DX11"))
                .monitors(new Monitor[] = {new Monitor("acer-s7"), new Monitor("acer-s7")})
                .ram(new Memory[] = {new Memory("2GB"), new Memory("2GB"), new Memory("2GB"), new Memory("2GB")})
                .build();
    }

}

This example shows how the builder pattern can allow a lot of flexibility in how a class is created with fairly little effort. The Computer object can be implemented based on the callers desired configuration in an easy to read manner with little effort.