Design Pattern Evangelist Blog

Smart pointers about software design

Maintained by Jim Humelsine | RSS Feed | Edit on GitHub

Builder Design Pattern - Basic Implementation

From Constructor to Basic Builder


Introduction

This blog entry walks you through how to apply the Builder Pattern in Java—from constructors with long parameter lists, to fluent setters, to a complete basic Builder implementation. Along the way, we’ll discuss design tradeoffs, give you practical code, and explain why the Builder Pattern is still relevant.

You’ve Tried the Rest, Now Try the Best

Pizza

The bulk of this blog post will feature Java code demonstrating a complex object being declared via a traditional constructor with implementation updates until it’s declared via a Builder.

The complex object will be a Pizza class for which instances declare the pizza’s size with optional toppings.

I’m only concerned with declaring a Pizza instance in this blog. There won’t be any behavior related methods except for a toString() method, which formats and returns a String descripiton of the Pizza instance.

Constructor Implementation

My initial design declares Pizza with a constructor that declares the size and several toppings as constructor arguments:

import java.util.*;

public class PizzaBuilder1 {
    public static void main(String[] args) {
        Pizza pizza1 = new Pizza(Pizza.Size.LARGE, true, false, true, false);

        System.out.println(pizza1);

        Pizza pizza2 = new Pizza(Pizza.Size.MEDIUM, true, true, true, true);

        System.out.println(pizza2);

        Pizza pizza3 = new Pizza(Pizza.Size.SMALL, false, false, false, false);

        System.out.println(pizza3);
    }

}

public class Pizza {

    public enum Size {
        SMALL, MEDIUM, LARGE
    }

    private final Size size;
    private final boolean pepperoni;
    private final boolean peppers;
    private final boolean onions;
    private final boolean blackOlives;

    public Pizza(Size size, boolean pepperoni, boolean peppers, boolean onions, boolean blackOlives) {
        this.size = size;
        this.pepperoni = pepperoni;
        this.peppers = peppers;
        this.onions = onions;
        this.blackOlives = blackOlives;
    }

    @Override
    public String toString() {
        StringBuilder description = new StringBuilder();
        description.append("Pizza Size: ").append(size);

        description.append(" with toppings:");
        boolean hasToppings = false;

        if (pepperoni) {
            description.append(" Pepperoni");
            hasToppings = true;
        }
        if (peppers) {
            if (hasToppings) description.append(",");
            description.append(" Peppers");
            hasToppings = true;
        }
        if (onions) {
            if (hasToppings) description.append(",");
            description.append(" Onions");
            hasToppings = true;
        }
        if (blackOlives) {
            if (hasToppings) description.append(",");
            description.append(" Black Olives");
            hasToppings = true;
        }

        if (!hasToppings) {
            description.append(" None");
        }

        return description.toString();
    }
}

Pizza is a Value Object, which I’ll blog about in the future (TBD). Its attributes are final, meaning that a Pizza object is immutable, which is a desirable trait, but there are few parts of this basic design that I don’t like:

NOTE: The only issue I have with this Pizza implementation is the number of parameters defined in the constructor, especially since the number of parameters could continue to accumulate. If Pizza had a smaller stable constructor parameter list with types providing context, then I would have no issues with that implementation. Most classes tend to have stable context oriented constructor parameter lists.

Constructor to Set Accessors

There’s more than one way to inject field attribute values into an object. The constructor is one option, and set accessors are another.

Here’s an implementation that reduces the constructor to one argument for the Pizza Size and allows the client code to add toppings as needed via set accessor methods:

import java.util.*;

public class PizzaBuilder2 {
    public static void main(String[] args) {
        Pizza pizza1 = new Pizza(Pizza.Size.LARGE);
        pizza1.addPepperoni();
        pizza1.addOnions();

        System.out.println(pizza1);

        Pizza pizza2 = new Pizza(Pizza.Size.MEDIUM);
        pizza2.addPepperoni();
        pizza2.addPeppers();
        pizza2.addOnions();
        pizza2.addBlackOlives();

        System.out.println(pizza2);

        Pizza pizza3 = new Pizza(Pizza.Size.SMALL);

        System.out.println(pizza3);
    }

}

public class Pizza {

    public enum Size {
        SMALL, MEDIUM, LARGE
    }

    private final Size size;
    private boolean pepperoni = false;
    private boolean peppers = false;
    private boolean onions = false;
    private boolean blackOlives = false;

    public Pizza(Size size) {
        this.size = size;
    }

    public void addPepperoni() {
        pepperoni = true;
    }

    public void addPeppers() {
        peppers = true;
    }

    public void addOnions() {
        onions = true;
    }

    public void addBlackOlives() {
        blackOlives = true;
    }

    @Override
    public String toString() {
        // Same toString() implementation as above. It will not change as the design progresses.
    }
}

This solves the long constructor argument problem, and it provides more context, since the accessors have meaningful names. But it’s broken the class initialization into multiple statements, and we’ve lost the immutable nature of the previous implementation. A new topping can be added at any time, even after the order were filled, which probably isn’t desirable.

When using a mechanism like this, we have to ensure the default behavior for any element not set still makes sense. It’s not an issue for Pizza toppings, since the absense of listing a topping just means that the topping is not desired for the pizza. For example, a Pizza without toppings is a plain cheese pizza, which is a perfectly valid choice.

Chaining Accessors

This update modifies the accessors slightly so that they return this rather than void. This allows us to chain the Pizza construction making it feel more like a unit:

import java.util.*;

public class PizzaBuilder3 {
    public static void main(String[] args) {
        Pizza pizza1 = new Pizza(Pizza.Size.LARGE)
            .addPepperoni()
            .addOnions();

        System.out.println(pizza1);

        Pizza pizza2 = new Pizza(Pizza.Size.MEDIUM)
            .addPepperoni()
            .addPeppers()
            .addOnions()
            .addBlackOlives();

        System.out.println(pizza2);

        Pizza pizza3 = new Pizza(Pizza.Size.SMALL);

        System.out.println(pizza3);
    }

}

public class Pizza {

    public enum Size {
        SMALL, MEDIUM, LARGE
    }

    private final Size size;
    private boolean pepperoni = false;
    private boolean peppers = false;
    private boolean onions = false;
    private boolean blackOlives = false;

    public Pizza(Size size) {
        this.size = size;
    }

    public Pizza addPepperoni() {
        pepperoni = true;
        return this;
    }

    public Pizza addPeppers() {
        peppers = true;
        return this;
    }

    public Pizza addOnions() {
        onions = true;
        return this;
    }

    public Pizza addBlackOlives() {
        blackOlives = true;
        return this;
    }

    @Override
    public String toString() {
        // Same toString() implementation original.
    }

}

This is a bit better, but it’s just syntactic sugar. The Pizza object is still mutable and a new topping can be added at any time, including after the order has been filled.

Chaining is an implementation style that often emerges when violating the Law of Demeter. Chaining pulls more classes and their dependencies into the code that uses it. However, this type of Pizza chaining is not a violation of the Law of Demter, since each link the chain still references the Pizza instance. No new classes or dependencies have been introduced.

The Pizza.Builder

This implementation is the union of the first and third implementations, where Pizza is immutable, and Pizza.Builder allows non-Demeter violating chaining. For a more detailed description of this technique, see Exploring Joshua Bloch’s Builder design pattern in Java.

import java.util.*;

public class PizzaBuilder4 {
    public static void main(String[] args) {
        Pizza pizza1 = new Pizza.Builder(Pizza.Size.LARGE)
            .addPepperoni()
            .addOnions()
            .build();

        System.out.println(pizza1);

        Pizza pizza2 = new Pizza.Builder(Pizza.Size.MEDIUM)
            .addPepperoni()
            .addPeppers()
            .addOnions()
            .addBlackOlives()
            .build();

        System.out.println(pizza2);

        Pizza pizza3 = new Pizza.Builder(Pizza.Size.SMALL)
            .build();

        System.out.println(pizza3);
    }

}

public class Pizza {

    public enum Size {
        SMALL, MEDIUM, LARGE
    }

    private final Size size;
    private final boolean pepperoni;
    private final boolean peppers;
    private final boolean onions;
    private final boolean blackOlives;

    private Pizza(Builder builder) {
        this.size = builder.size;
        this.pepperoni = builder.pepperoni;
        this.peppers = builder.peppers;
        this.onions = builder.onions;
        this.blackOlives = builder.blackOlives;
    }

    public static class Builder {
        private Size size;
        private boolean pepperoni = false;
        private boolean peppers = false;
        private boolean onions = false;
        private boolean blackOlives = false;

        public Builder(Size size) {
            this.size = size;
        }

        public Builder addPepperoni() {
            pepperoni = true;
            return this;
        }

        public Builder addPeppers() {
            peppers = true;
            return this;
        }

        public Builder addOnions() {
            onions = true;
            return this;
        }

        public Builder addBlackOlives() {
            blackOlives = true;
            return this;
        }

        public Pizza build() {
            return new Pizza(this);
        }
    }
    @Override
    public String toString() {
        // Same toString() implementation original.
    }


}

General Builder by GoF

This implementation includes several of the attributes of the Builder pattern presented in the Builder Design Pattern Introduction blog entry:

Builder Sequence Diagram by GoF

There are several Builder attributes that are not included in this example; although, this is also the case for the Real World Examples from the previous blog:

I will be adding these missing attributes in subsequent blog entries.

There’s a bit more code and complexity to this implementation than in the earlier implementation examples, so each developer or team will need to decide if it’s worth the investment. However, this PizzaDriver implementation ensures that Pizza can only be constructed via the Pizza.Builder and once built, the Pizza instance is immutable.

Summary

The Builder pattern isn’t just a way to reduce constructor clutter—it’s a tool that brings clarity and control to object creation. Try applying it in your next project when you need flexible, readable instantiation of complex objects. And stay tuned—we’ll look at how to structure reusable builders and abstract the process even further in the next article.

References

See: Previous Blog References, which provides an extensive list of Builder resources.

Complete Demo Code

Here’s the entire implementation up to this point as one file. Copy and paste it into a Java environment and execute it. If you don’t have Java, try this Online Java Environment. Add more tests. Play with the implementation. Refactor some of the code.

Constructor

import java.util.*;

public class PizzaBuilder1 {
    public static void main(String[] args) {
        Pizza pizza1 = new Pizza(Pizza.Size.LARGE, true, false, true, false);

        System.out.println(pizza1);

        Pizza pizza2 = new Pizza(Pizza.Size.MEDIUM, true, true, true, true);

        System.out.println(pizza2);

        Pizza pizza3 = new Pizza(Pizza.Size.SMALL, false, false, false, false);

        System.out.println(pizza3);
    }

}

public class Pizza {

    public enum Size {
        SMALL, MEDIUM, LARGE
    }

    private final Size size;
    private final boolean pepperoni;
    private final boolean peppers;
    private final boolean onions;
    private final boolean blackOlives;

    public Pizza(Size size, boolean pepperoni, boolean peppers, boolean onions, boolean blackOlives) {
        this.size = size;
        this.pepperoni = pepperoni;
        this.peppers = peppers;
        this.onions = onions;
        this.blackOlives = blackOlives;
    }

    @Override
    public String toString() {
        StringBuilder description = new StringBuilder();
        description.append("Pizza Size: ").append(size);

        description.append(" with toppings:");
        boolean hasToppings = false;

        if (pepperoni) {
            description.append(" Pepperoni");
            hasToppings = true;
        }
        if (peppers) {
            if (hasToppings) description.append(",");
            description.append(" Peppers");
            hasToppings = true;
        }
        if (onions) {
            if (hasToppings) description.append(",");
            description.append(" Onions");
            hasToppings = true;
        }
        if (blackOlives) {
            if (hasToppings) description.append(",");
            description.append(" Black Olives");
            hasToppings = true;
        }

        if (!hasToppings) {
            description.append(" None");
        }

        return description.toString();
    }
}

Set Accessors

import java.util.*;

public class PizzaBuilder2 {
    public static void main(String[] args) {
        Pizza pizza1 = new Pizza(Pizza.Size.LARGE);
        pizza1.addPepperoni();
        pizza1.addOnions();

        System.out.println(pizza1);

        Pizza pizza2 = new Pizza(Pizza.Size.MEDIUM);
        pizza2.addPepperoni();
        pizza2.addPeppers();
        pizza2.addOnions();
        pizza2.addBlackOlives();

        System.out.println(pizza2);

        Pizza pizza3 = new Pizza(Pizza.Size.SMALL);

        System.out.println(pizza3);
    }

}

public class Pizza {

    public enum Size {
        SMALL, MEDIUM, LARGE
    }

    private final Size size;
    private boolean pepperoni = false;
    private boolean peppers = false;
    private boolean onions = false;
    private boolean blackOlives = false;

    public Pizza(Size size) {
        this.size = size;
    }

    public void addPepperoni() {
        pepperoni = true;
    }

    public void addPeppers() {
        peppers = true;
    }

    public void addOnions() {
        onions = true;
    }

    public void addBlackOlives() {
        blackOlives = true;
    }

    @Override
    public String toString() {
        StringBuilder description = new StringBuilder();
        description.append("Pizza Size: ").append(size);

        description.append(" with toppings:");
        boolean hasToppings = false;

        if (pepperoni) {
            description.append(" Pepperoni");
            hasToppings = true;
        }
        if (peppers) {
            if (hasToppings) description.append(",");
            description.append(" Peppers");
            hasToppings = true;
        }
        if (onions) {
            if (hasToppings) description.append(",");
            description.append(" Onions");
            hasToppings = true;
        }
        if (blackOlives) {
            if (hasToppings) description.append(",");
            description.append(" Black Olives");
            hasToppings = true;
        }

        if (!hasToppings) {
            description.append(" None");
        }

        return description.toString();
    }
}

Chaining

import java.util.*;

public class PizzaBuilder3 {
    public static void main(String[] args) {
        Pizza pizza1 = new Pizza(Pizza.Size.LARGE)
            .addPepperoni()
            .addOnions();

        System.out.println(pizza1);

        Pizza pizza2 = new Pizza(Pizza.Size.MEDIUM)
            .addPepperoni()
            .addPeppers()
            .addOnions()
            .addBlackOlives();

        System.out.println(pizza2);

        Pizza pizza3 = new Pizza(Pizza.Size.SMALL);

        System.out.println(pizza3);
    }

}

public class Pizza {

    public enum Size {
        SMALL, MEDIUM, LARGE
    }

    private final Size size;
    private boolean pepperoni = false;
    private boolean peppers = false;
    private boolean onions = false;
    private boolean blackOlives = false;

    public Pizza(Size size) {
        this.size = size;
    }

    public Pizza addPepperoni() {
        pepperoni = true;
        return this;
    }

    public Pizza addPeppers() {
        peppers = true;
        return this;
    }

    public Pizza addOnions() {
        onions = true;
        return this;
    }

    public Pizza addBlackOlives() {
        blackOlives = true;
        return this;
    }

    @Override
    public String toString() {
        StringBuilder description = new StringBuilder();
        description.append("Pizza Size: ").append(size);

        description.append(" with toppings:");
        boolean hasToppings = false;

        if (pepperoni) {
            description.append(" Pepperoni");
            hasToppings = true;
        }
        if (peppers) {
            if (hasToppings) description.append(",");
            description.append(" Peppers");
            hasToppings = true;
        }
        if (onions) {
            if (hasToppings) description.append(",");
            description.append(" Onions");
            hasToppings = true;
        }
        if (blackOlives) {
            if (hasToppings) description.append(",");
            description.append(" Black Olives");
            hasToppings = true;
        }

        if (!hasToppings) {
            description.append(" None");
        }

        return description.toString();
    }
}

Builder

import java.util.*;

public class PizzaBuilder4 {
    public static void main(String[] args) {
        Pizza pizza1 = new Pizza.Builder(Pizza.Size.LARGE)
            .addPepperoni()
            .addOnions()
            .build();

        System.out.println(pizza1);

        Pizza pizza2 = new Pizza.Builder(Pizza.Size.MEDIUM)
            .addPepperoni()
            .addPeppers()
            .addOnions()
            .addBlackOlives()
            .build();

        System.out.println(pizza2);

        Pizza pizza3 = new Pizza.Builder(Pizza.Size.SMALL)
            .build();

        System.out.println(pizza3);
    }

}

public class Pizza {

    public enum Size {
        SMALL, MEDIUM, LARGE
    }

    private final Size size;
    private final boolean pepperoni;
    private final boolean peppers;
    private final boolean onions;
    private final boolean blackOlives;

    private Pizza(Builder builder) {
        this.size = builder.size;
        this.pepperoni = builder.pepperoni;
        this.peppers = builder.peppers;
        this.onions = builder.onions;
        this.blackOlives = builder.blackOlives;
    }

    public static class Builder {
        private Size size;
        private boolean pepperoni = false;
        private boolean peppers = false;
        private boolean onions = false;
        private boolean blackOlives = false;

        public Builder(Size size) {
            this.size = size;
        }

        public Builder addPepperoni() {
            pepperoni = true;
            return this;
        }

        public Builder addPeppers() {
            peppers = true;
            return this;
        }

        public Builder addOnions() {
            onions = true;
            return this;
        }

        public Builder addBlackOlives() {
            blackOlives = true;
            return this;
        }

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

    @Override
    public String toString() {
        StringBuilder description = new StringBuilder();
        description.append("Pizza Size: ").append(size);

        description.append(" with toppings:");
        boolean hasToppings = false;

        if (pepperoni) {
            description.append(" Pepperoni");
            hasToppings = true;
        }
        if (peppers) {
            if (hasToppings) description.append(",");
            description.append(" Peppers");
            hasToppings = true;
        }
        if (onions) {
            if (hasToppings) description.append(",");
            description.append(" Onions");
            hasToppings = true;
        }
        if (blackOlives) {
            if (hasToppings) description.append(",");
            description.append(" Black Olives");
            hasToppings = true;
        }

        if (!hasToppings) {
            description.append(" None");
        }

        return description.toString();
    }
}

Previous: Builder Design Pattern Introduction

Next: Builder Design Pattern Completed

Home: Design Pattern Evangelist Blog