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:
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.
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.
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:
List<String> specification
represents a specification that consists of multiple lines of strings. The specification origin may have been a text file or the selections on an app by the customer, which have been converted into a list of strings.PizzaBuilder
is the interface to which thePizzaDirector
will direct its construction.
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.
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:
Client
no longer hardcodes the pizza. The pizza order resides within the specification. The other parts ofClient
are mostly the same as with the previous design.- Except for moving the
PizzaSize
declaration from theStandardPizzaBuilder
to thePizzaBuilder
interface,StandardPizzaBuilder
andPizzaBuilder
have not changed. Pizza
has never changed. Since it has no dependencies upon the rest of the design. This can be observed visually in the design, since all arrows of dependency and knowledge point into it.
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:
CaloriePizzaBuiler
has been added.- The
Client
has been updated, but barely. It creates aCaloriePizzaBuilder
rather than aStandardPizzaBuilder
.calories
is a basic type rather than an object. - Nothing else in the design changes.
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.
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;
}
}