Design Pattern Evangelist Blog

Smart pointers about software design

Maintained by Jim Humelsine | RSS Feed | Edit on GitHub

Builder Design Pattern Completed

Progressing toward the Gang of Four’s complete Builder design


Introduction

In the previous post, Builder Design Pattern - Basic Implementation, I showed the progression of creating a complex object from a multi-parameter constructor to a basic inner-class Builder pattern. The inner-class Builder pattern worked, but it was tightly coupled to the product and left little room for flexibility. In this article, we’ll take the next step—refactoring that design into a full Gang of Four-style Builder. Along the way, we’ll introduce a Director, which will construct the complex object based upon a specification. We’ll also decouple the Builder from the Product and explore how multiple Builders can create different representations of the same object. By the end, you’ll see how the Builder pattern provides not just convenience, but also powerful extensibility.

This blog will continue where the previous blog left off by progressing step-by-step until it reaches a design more akin to the GoF’s full Builder diagram:

RTF Builder by GoF

These step-by-step transitions will pull the elements of the design apart along their seams. There will be a little messiness as I progress. It’s like reorganizing a room. You need to pull everything apart before you can organize the elements to put it back together.

This blog entry will primarily focus upon the design elements and implementation snippets in transition toward the GoF’s Builder design.

Addressing Tight Coupling

The previous blog concluded with the Pizza.Builder design, which is a Builder, but a rather simple one. Pizza.Builder was declared as an inner class of Pizza, which tightly coupled the two classes. For some designs this may make perfect sense. However, this tight coupling won’t support the remaining GoF Builder behaviors that I want to convey; therefore, this next tranistion will remove their mutual coupling by extracting the Builder from Pizza, so that Pizza no longer depends upon the Builder class.

Here is an intermediate redesign moving toward the GoF’s Builder; however, this would not be my final design if this were more than an example. I must make some accommodations in this design that I don’t particularly like, such as declaring Pizza constructor as package-protected, but these accommodations will make it easier to transition to the full GoF Builder design. I am mostly including this design as a transient transitional phase in moving toward the final design.

Here is the updated UML diagram with separate PizzaBuilder and Pizza class definitions. Pizza no longer has knowledge of nor depends upon PizzaBuilder. I also added a Client class to show how it uses PizzaBuilder to build a Pizza instance. The Client is the Configurer in this design, and it will be the Configurer throughout these design phases.

Pizza/PizzaBuilder decoupling

Here is the code:

import java.util.*;

public class PizzaBuilder1 {
    public static void main(String[] args) {
        Pizza pizza1 = new PizzaBuilder(PizzaSize.LARGE)
            .addPepperoni()
            .addOnions()
            .build();
        System.out.println(pizza1);

        Pizza pizza2 = new PizzaBuilder(PizzaSize.MEDIUM)
            .addPepperoni()
            .addPeppers()
            .addOnions()
            .addBlackOlives()
            .build();
        System.out.println(pizza2);

        Pizza pizza3 = new PizzaBuilder(PizzaSize.SMALL).build();
        System.out.println(pizza3);
    }

}

enum PizzaSize {
    SMALL, MEDIUM, LARGE
}

public class PizzaBuilder {
    private PizzaSize size;
    private boolean pepperoni = false;
    private boolean peppers = false;
    private boolean onions = false;
    private boolean blackOlives = false;

    public PizzaBuilder(PizzaSize size) {
        this.size = size;
    }

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

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

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

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

    public Pizza build() {
        return new Pizza(size, pepperoni, peppers, onions, blackOlives);
    }
}

class Pizza {
    private final PizzaSize size;
    private final boolean pepperoni;
    private final boolean peppers;
    private final boolean onions;
    private final boolean blackOlives;

    Pizza(PizzaSize 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();
    }
}

Extracting a PizzaBuilder Interface

The GoF’s Builder example declares a Builder interface with several concrete Builder classes. This will allow the design to support multiple concrete Builder classes.

In this phase, I’m going to convert PizzaBuilder into an interface and move its implementation to StandardPizzaBuilder. I have also removed chaining. Most of the rest of the design remains the same.

Client must still access the concrete Builder, now named StandardPizzaBuilder. That’s because build() is not declared in the PizzaBuilder interface. The interface only defines methods that define pizza toppings.

Each concrete class that implements PizzaBuilder can build its own type of product, which will become more obvious when I add a second concrete class. Concrete PizzaBuilder classes are not obligated to create a Pizza as their product.

Since the Client creates a Pizza in this example, it will still reference the StandardPizzaBuilder class so that it has access to its build() method.

PizzaBuilder Interface

Here are the updated Java snippets. The entire implementation for each design phase is provided in the Complete Demo Code.

interface PizzaBuilder {
    void addPepperoni();

    void addPeppers();

    void addOnions();

    void addBlackOlives();
}

public class StandardPizzaBuilder implements PizzaBuilder {
    private PizzaSize size;
    private boolean pepperoni = false;
    private boolean peppers = false;
    private boolean onions = false;
    private boolean blackOlives = false;

    public StandardPizzaBuilder(PizzaSize size) {
        this.size = size;
    }

    @Override
    public void addPepperoni() {
        pepperoni = true;
    }

    @Override
    public void addPeppers() {
        peppers = true;
    }

    @Override
    public void addOnions() {
        onions = true;
    }

    @Override
    public void addBlackOlives() {
        blackOlives = true;
    }

    public Pizza build() {
        return new Pizza(size, pepperoni, peppers, onions, blackOlives);
    }
}

Adding the Director

Every Builder example I have provided thus far has featured a hardcoded construction. If the Client wishes to build a pizza with different combinations of toppings, then that will require a code change. I want more flexibility in the design. I want a customer to be able to customize their pizza. I want them to be able to put any combinations of toppings on it without having to update the implementation.

The GoF’s Builder design includes a Director that will accommodate customized complex object construction. The Director implements a method that the GoF called parseRTF() in their domain specific example and Construct() in their more generalized example. This method parses a specification, identifies construction details and delegates them to a Builder interface, whose reference has been provided as a method parameter. That is, Construct() does not know nor depend upon a specific concrete Builder. It only depends upon the Builder interface contract.

This first diagram features PizzaDirector. Its construct() method declares two parameters:

setSize(PizzaSize size) has also been added to PizzaBuilder thereby placing it in the PizzaBuilder interface contract rather than its previous declaration as a concrete constructor parameter. This makes more sense to me, since PizzaBuilder is about declaring the type of pizza desired by the customer. A pizza’s size and toppings feel like cohesive declaration elements of a pizza that should exist together in an interface.

construct(specification, pizzaBuilder) iterates the specification one line at a time and based upon the spec value, it delegates to the appropriate PizzaBuilder method. construct(specification, pizzaBuilder) only initializes the PizzaBuilder by telling it the pizza size and toppings. It does not build the pizza itself.

PizzaDirector details

The previous diagram is only part of the design. construct(…) required too much space to fit the entire design on one diagram.

Here’s the complete design where the construct(...) details have been removed. A few items of note:

Complete Design with PizzaDirector

Here is the code for the updated elements in this design:

StandardPizzaBuilder pizzaBuilder1 = new StandardPizzaBuilder();
PizzaDirector.construct(getLargePizzaSpecification(), pizzaBuilder1);
Pizza pizza1 = pizzaBuilder1.build();
System.out.println(pizza1);

private static List<String> getLargePizzaSpecification() {
    List<String> specification = new LinkedList<>();

    specification.add("Large");
    specification.add("pepperoni");
    specification.add("onions");

    return specification;
}

class PizzaDirector {

    public static void construct(List<String> specification, PizzaBuilder pizzaBuilder) throws Exception {
        for (String spec : specification) {
            switch (spec.toUpperCase()) {
                case "LARGE": pizzaBuilder.setSize(PizzaSize.LARGE); break;
                case "MEDIUM": pizzaBuilder.setSize(PizzaSize.MEDIUM); break;
                case "SMALL": pizzaBuilder.setSize(PizzaSize.SMALL); break;
                case "PEPPERONI": pizzaBuilder.addPepperoni(); break;
                case "PEPPERS": pizzaBuilder.addPeppers(); break;
                case "ONIONS": pizzaBuilder.addOnions(); break;
                case "BLACK OLIVES": pizzaBuilder.addBlackOlives(); break;
                default: throw new Exception("Unknown spec=" + spec);
            }
        }
    }
}

public class StandardPizzaBuilder implements PizzaBuilder {
    private PizzaSize size = null;
    private boolean pepperoni = false;
    private boolean peppers = false;
    private boolean onions = false;
    private boolean blackOlives = false;

    public void setSize(PizzaSize size) {
        this.size = size;
    }

    public void addPepperoni() {
        pepperoni = true;
    }

    public void addPeppers() {
        peppers = true;
    }

    public void addOnions() {
        onions = true;
    }

    public void addBlackOlives() {
        blackOlives = true;
    }

    public Pizza build() {
        return new Pizza(size, pepperoni, peppers, onions, blackOlives);
    }
}

A New Concrete PizzaBuilder

The GoF also featured several concrete Builders. This design allows different concrete Builders to create different products from the same Builder interface construction. This aspect of the Builder pattern features the Strategy Design Pattern.

In this final design I’ll add CaloriePizzaBuilder, which is another PizzaBuilder. This design shows how simple it is to add a new concrete PizzaBuilder once the design infrastructure has stabilized. Its addition does not affect the rest of the design.

CaloriePizzaBuilder’s product is the number of calories in the pizza, which is returned as an int.

Here’s the design that supports CaloriePizzaBuilder. Here are a few items of note:

CaloriePizzaBuilder

Here are snippets of the updated code. The PizzaSize acts as a scalar to increase the number of calories proportionally with larger pizzas.

StandardPizzaBuilder pizzaBuilder1 = new StandardPizzaBuilder();
PizzaDirector.construct(getLargePizzaSpecification(), pizzaBuilder1);
Pizza pizza1 = pizzaBuilder1.build();
System.out.println(pizza1);

public class CaloriePizzaBuilder implements PizzaBuilder {
    private double sizeScalar = 0.0;
    private int calories = 0;

    public void setSize(PizzaSize size) {
        sizeScalar = getSizeScalar(size);
        calories += sizeScalar * 1000.0;
    }

    private double getSizeScalar(PizzaSize size) {
        switch (size) {
            case SMALL: return 1.0;
            case MEDIUM: return 1.5;
            case LARGE: return 2.0;
            default: return 0.0;
        }
    }

    public void addPepperoni() {
        calories += sizeScalar * 250;
    }

    public void addPeppers() {
        calories += sizeScalar * 30;
    }

    public void addOnions() {
        calories += sizeScalar * 25;
    }

    public void addBlackOlives() {
        calories += sizeScalar * 100;
    }

    public int getCalories() {
        return calories;
    }
}

The Complete Design

This final diagram shows all elements in the design. Its structure mirrors the GoF’s example at the top of this blog, which is also available here.

Once the entire design is diagramed, it becomes obvious that the Strategy Design Pattern is a major design element in this design. For example, this design can easily accommodate more concrete PizzaBuilders such as PricePizzaBuilder, which would look very similar to CaloriePizzaBuilder, but rather than calculating calories, it would calculate the cost of the built pizza.

Complete Design

A Review of the Builder Evolution

Here is a table summarizing the evolution of the Builder design from inner-class to complete GoF design.

Phase Description Key Characteristics Pros Cons
1. Inner-Class Builder (Basic Impl) Builder nested inside Pizza class - Fluent API for constructing Pizza
- Builder tightly coupled to Product
- Simple to implement
- Easy to read and use
- Limited flexibility
- Hard to extend beyond Pizza
2. Decoupled Builder (Standalone Class) Builder extracted into its own PizzaBuilder class - Builder separated from Product
- Still tailored to a single Pizza product
- Reduces coupling
- More modular design
- Still requires client to manage construction logic
3. Abstract Builder Interface Defined a Builder interface for generalization - Supports multiple concrete Builders
- Moves closer to GoF definition
- Enables polymorphism
- Easier to extend to other products
- Adds boilerplate
- Still leaves orchestration with client
4. Director + Specification Introduced Director to orchestrate construction - Director encapsulates build steps
- Specification controls configuration
- Removes construction logic from client
- Consistent object creation process
- More complex design
- Requires upfront specification model
5. Alternate Concrete Builders (e.g., CaloriePizzaBuilder) Multiple Builders reuse same process for different outputs - One Builder creates Pizza
- Another computes calorie totals
- Demonstrates extensibility
- Supports different “representations” from same steps
- Extra classes to maintain
- May feel heavyweight for simple cases
Takeaway Builder evolves from a convenience tool into a full design pattern - Separation of construction from representation
- Flexibility through abstraction
- Scales to complex systems
- Encourages clean, maintainable code
- Adds design overhead
- Unnecessary for simple objects

But Wait, There’s More

There’s one more aspect of Builder that’s useful. I think it’s the most important aspect of Builder, but the GoF don’t really mention it. I’ll cover that in the next and final Builder blog entry.

Summary

Builder isn’t just about chaining setters—it’s about separating the “what” from the “how.” By decoupling the Builder from the Product and introducing a Director, we gained flexibility and extensibility. With alternate Builders, we opened the door to new ways of representing the same construction steps. This is the difference between a convenience trick and a true design pattern. In the next post, we’ll explore one aspect of Builder which the GoF mostly ignored.

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.

Addressing Tight Coupling

import java.util.*;

public class PizzaBuilder1 {
    public static void main(String[] args) {
        Pizza pizza1 = new PizzaBuilder(PizzaSize.LARGE)
            .addPepperoni()
            .addOnions()
            .build();
        System.out.println(pizza1);

        Pizza pizza2 = new PizzaBuilder(PizzaSize.MEDIUM)
            .addPepperoni()
            .addPeppers()
            .addOnions()
            .addBlackOlives()
            .build();
        System.out.println(pizza2);

        Pizza pizza3 = new PizzaBuilder(PizzaSize.SMALL).build();
        System.out.println(pizza3);
    }

}

enum PizzaSize {
    SMALL, MEDIUM, LARGE
}

public class PizzaBuilder {
    private PizzaSize size;
    private boolean pepperoni = false;
    private boolean peppers = false;
    private boolean onions = false;
    private boolean blackOlives = false;

    public PizzaBuilder(PizzaSize size) {
        this.size = size;
    }

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

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

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

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

    public Pizza build() {
        return new Pizza(size, pepperoni, peppers, onions, blackOlives);
    }
}

class Pizza {
    private final PizzaSize size;
    private final boolean pepperoni;
    private final boolean peppers;
    private final boolean onions;
    private final boolean blackOlives;

    Pizza(PizzaSize 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();
    }
}

Extracting a PizzaBuilder Interface

import java.util.*;

public class PizzaBuilder2 {
    public static void main(String[] args) {
        StandardPizzaBuilder pizzaBuilder1 = new StandardPizzaBuilder(PizzaSize.LARGE);
        pizzaBuilder1.addPepperoni();
        pizzaBuilder1.addOnions();
        Pizza pizza1 = pizzaBuilder1.build();
        System.out.println(pizza1);

        StandardPizzaBuilder pizzaBuilder2 = new StandardPizzaBuilder(PizzaSize.MEDIUM);
        pizzaBuilder2.addPepperoni();
        pizzaBuilder2.addPeppers();
        pizzaBuilder2.addOnions();
        pizzaBuilder2.addBlackOlives();
        Pizza pizza2 = pizzaBuilder2.build();
        System.out.println(pizza2);

        StandardPizzaBuilder pizzaBuilder3 = new StandardPizzaBuilder(PizzaSize.SMALL);
        Pizza pizza3 = pizzaBuilder3.build();
        System.out.println(pizza3);
    }

}

enum PizzaSize {
    SMALL, MEDIUM, LARGE
}

interface PizzaBuilder {
    void addPepperoni();

    void addPeppers();

    void addOnions();

    void addBlackOlives();
}

public class StandardPizzaBuilder implements PizzaBuilder {
    private PizzaSize size;
    private boolean pepperoni = false;
    private boolean peppers = false;
    private boolean onions = false;
    private boolean blackOlives = false;

    public StandardPizzaBuilder(PizzaSize size) {
        this.size = size;
    }

    @Override
    public void addPepperoni() {
        pepperoni = true;
    }

    @Override
    public void addPeppers() {
        peppers = true;
    }

    @Override
    public void addOnions() {
        onions = true;
    }

    @Override
    public void addBlackOlives() {
        blackOlives = true;
    }

    public Pizza build() {
        return new Pizza(size, pepperoni, peppers, onions, blackOlives);
    }
}

class Pizza {
    private final PizzaSize size;
    private final boolean pepperoni;
    private final boolean peppers;
    private final boolean onions;
    private final boolean blackOlives;

    Pizza(PizzaSize 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();
    }
}

Adding the Director

import java.util.*;

public class PizzaBuilder3 {
    public static void main(String[] args) throws Exception {
        StandardPizzaBuilder pizzaBuilder1 = new StandardPizzaBuilder();
        PizzaDirector.construct(getLargePizzaSpecification(), pizzaBuilder1);
        Pizza pizza1 = pizzaBuilder1.build();
        System.out.println(pizza1);

        StandardPizzaBuilder pizzaBuilder2 = new StandardPizzaBuilder();
        PizzaDirector.construct(getMediumPizzaSpecification(), pizzaBuilder2);
        Pizza pizza2 = pizzaBuilder2.build();
        System.out.println(pizza2);

        StandardPizzaBuilder pizzaBuilder3 = new StandardPizzaBuilder();
        PizzaDirector.construct(getSmallPizzaSpecification(), pizzaBuilder3);
        Pizza pizza3 = pizzaBuilder3.build();
        System.out.println(pizza3);
    }

    private static List<String> getLargePizzaSpecification() {
        List<String> specification = new LinkedList<>();

        specification.add("Large");
        specification.add("pepperoni");
        specification.add("onions");

        return specification;
    }

    private static List<String> getMediumPizzaSpecification() {
        List<String> specification = new LinkedList<>();

        specification.add("Medium");
        specification.add("pepperoni");
        specification.add("peppers");
        specification.add("onions");
        specification.add("black olives");

        return specification;
    }

    private static List<String> getSmallPizzaSpecification() {
        List<String> specification = new LinkedList<>();

        specification.add("Small");

        return specification;
    }

}

enum PizzaSize {
    SMALL, MEDIUM, LARGE
}

class PizzaDirector {

    public static void construct(List<String> specification, PizzaBuilder pizzaBuilder) throws Exception {
        for (String spec : specification) {
            switch (spec.toUpperCase()) {
                case "LARGE": pizzaBuilder.setSize(PizzaSize.LARGE); break;
                case "MEDIUM": pizzaBuilder.setSize(PizzaSize.MEDIUM); break;
                case "SMALL": pizzaBuilder.setSize(PizzaSize.SMALL); break;
                case "PEPPERONI": pizzaBuilder.addPepperoni(); break;
                case "PEPPERS": pizzaBuilder.addPeppers(); break;
                case "ONIONS": pizzaBuilder.addOnions(); break;
                case "BLACK OLIVES": pizzaBuilder.addBlackOlives(); break;
                default: throw new Exception("Unknown spec=" + spec);
            }
        }
    }
}

interface PizzaBuilder {
    void setSize(PizzaSize size);

    void addPepperoni();

    void addPeppers();

    void addOnions();

    void addBlackOlives();
}

public class StandardPizzaBuilder implements PizzaBuilder {
    private PizzaSize size = null;
    private boolean pepperoni = false;
    private boolean peppers = false;
    private boolean onions = false;
    private boolean blackOlives = false;

    public void setSize(PizzaSize size) {
        this.size = size;
    }

    public void addPepperoni() {
        pepperoni = true;
    }

    public void addPeppers() {
        peppers = true;
    }

    public void addOnions() {
        onions = true;
    }

    public void addBlackOlives() {
        blackOlives = true;
    }

    public Pizza build() {
        return new Pizza(size, pepperoni, peppers, onions, blackOlives);
    }
}

class Pizza {
    private final PizzaSize size;
    private final boolean pepperoni;
    private final boolean peppers;
    private final boolean onions;
    private final boolean blackOlives;

    Pizza(PizzaSize size, boolean pepperoni, boolean peppers, boolean onions, boolean blackOlives) {
        if (size == null) throw new NullPointerException();
        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();
    }
}

A New Concrete PizzaBuilder

import java.util.*;

public class PizzaBuilder4 {
    public static void main(String[] args) throws Exception {
        StandardPizzaBuilder pizzaBuilder1 = new StandardPizzaBuilder();
        PizzaDirector.construct(getLargePizzaSpecification(), pizzaBuilder1);
        Pizza pizza1 = pizzaBuilder1.build();
        System.out.println(pizza1);

        StandardPizzaBuilder pizzaBuilder2 = new StandardPizzaBuilder();
        PizzaDirector.construct(getMediumPizzaSpecification(), pizzaBuilder2);
        Pizza pizza2 = pizzaBuilder2.build();
        System.out.println(pizza2);

        StandardPizzaBuilder pizzaBuilder3 = new StandardPizzaBuilder();
        PizzaDirector.construct(getSmallPizzaSpecification(), pizzaBuilder3);
        Pizza pizza3 = pizzaBuilder3.build();
        System.out.println(pizza3);

        int calories = 0;

        CaloriePizzaBuilder pizzaBuilder4 = new CaloriePizzaBuilder();
        PizzaDirector.construct(getLargePizzaSpecification(), pizzaBuilder4);
        calories = pizzaBuilder4.getCalories();
        System.out.format("Pizza has %d calories.\n", calories);

        CaloriePizzaBuilder pizzaBuilder5 = new CaloriePizzaBuilder();
        PizzaDirector.construct(getMediumPizzaSpecification(), pizzaBuilder5);
        calories = pizzaBuilder5.getCalories();
        System.out.format("Pizza has %d calories.\n", calories);

        CaloriePizzaBuilder pizzaBuilder6 = new CaloriePizzaBuilder();
        PizzaDirector.construct(getSmallPizzaSpecification(), pizzaBuilder6);
        calories = pizzaBuilder6.getCalories();
        System.out.format("Pizza has %d calories.\n", calories);
    }

    private static List<String> getLargePizzaSpecification() {
        List<String> specification = new LinkedList<>();

        specification.add("Large");
        specification.add("pepperoni");
        specification.add("onions");

        return specification;
    }

    private static List<String> getMediumPizzaSpecification() {
        List<String> specification = new LinkedList<>();

        specification.add("Medium");
        specification.add("pepperoni");
        specification.add("peppers");
        specification.add("onions");
        specification.add("black olives");

        return specification;
    }

    private static List<String> getSmallPizzaSpecification() {
        List<String> specification = new LinkedList<>();

        specification.add("Small");

        return specification;
    }

}

enum PizzaSize {
    SMALL, MEDIUM, LARGE
}

class PizzaDirector {

    public static void construct(List<String> specification, PizzaBuilder pizzaBuilder) throws Exception {
        for (String spec : specification) {
            switch (spec.toUpperCase()) {
                case "LARGE": pizzaBuilder.setSize(PizzaSize.LARGE); break;
                case "MEDIUM": pizzaBuilder.setSize(PizzaSize.MEDIUM); break;
                case "SMALL": pizzaBuilder.setSize(PizzaSize.SMALL); break;
                case "PEPPERONI": pizzaBuilder.addPepperoni(); break;
                case "PEPPERS": pizzaBuilder.addPeppers(); break;
                case "ONIONS": pizzaBuilder.addOnions(); break;
                case "BLACK OLIVES": pizzaBuilder.addBlackOlives(); break;
                default: throw new Exception("Unknown spec=" + spec);
            }
        }
    }
}

interface PizzaBuilder {
    void setSize(PizzaSize size);

    void addPepperoni();

    void addPeppers();

    void addOnions();

    void addBlackOlives();
}

public class StandardPizzaBuilder implements PizzaBuilder {
    private PizzaSize size = null;
    private boolean pepperoni = false;
    private boolean peppers = false;
    private boolean onions = false;
    private boolean blackOlives = false;

    public void setSize(PizzaSize size) {
        this.size = size;
    }

    public void addPepperoni() {
        pepperoni = true;
    }

    public void addPeppers() {
        peppers = true;
    }

    public void addOnions() {
        onions = true;
    }

    public void addBlackOlives() {
        blackOlives = true;
    }

    public Pizza build() {
        return new Pizza(size, pepperoni, peppers, onions, blackOlives);
    }
}

class Pizza {
    private final PizzaSize size;
    private final boolean pepperoni;
    private final boolean peppers;
    private final boolean onions;
    private final boolean blackOlives;

    Pizza(PizzaSize size, boolean pepperoni, boolean peppers, boolean onions, boolean blackOlives) {
        if (size == null) throw new NullPointerException();
        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();
    }
}

public class CaloriePizzaBuilder implements PizzaBuilder {
    private double sizeScalar = 0.0;
    private int calories = 0;

    public void setSize(PizzaSize size) {
        sizeScalar = getSizeScalar(size);
        calories += sizeScalar * 1000.0;
    }

    private double getSizeScalar(PizzaSize size) {
        switch (size) {
            case SMALL: return 1.0;
            case MEDIUM: return 1.5;
            case LARGE: return 2.0;
            default: return 0.0;
        }
    }

    public void addPepperoni() {
        calories += sizeScalar * 250;
    }

    public void addPeppers() {
        calories += sizeScalar * 30;
    }

    public void addOnions() {
        calories += sizeScalar * 25;
    }

    public void addBlackOlives() {
        calories += sizeScalar * 100;
    }

    public int getCalories() {
        return calories;
    }
}

Previous: Builder Design Pattern - Basic Implementation

Next: DRAFT – The Director - More Than Builder’s Sidekick

Home: Design Pattern Evangelist Blog