5
\$\begingroup\$

I make my original program more interesting and try to make the builder API more easier to read:

  • Now it's a static method of Builder: Pizza.Builder.recipeBuild(...) ,who bake the pizza with your customized Builder

  • Customer only need to setup her/his Builder, she/he doesn't have to know anything about the workflow about the recipe. But still, she/he can override some methods in the recipe to make the pizza more special.

  • I try to make it a builder pattern and it has all characters in Builder Pattern:

    • Director: recipeBuild
    • Builder: Pizza.Builder
    • Complex Product: Pizza
  • The recipe reference from here


Main.java

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ArrayList<String> myToppings = new ArrayList<String>();
        myToppings.add("Mushrooms");
        myToppings.add("Onions");
        myToppings.add("Black olives");
        myToppings.add("Pineapple");

        // Raw build edition.
        Pizza myPizza = new Pizza.Builder(Pizza.STANDARD)
                                 .withCustomizedToppings(myToppings)
                                 .build();
        System.out.format("%s%n%n", myPizza);

        // With build-in template, which is optional
        Pizza.Builder customizer = new Pizza.Builder(Pizza.ZORUA_SPECIAL)
                                            .withCustomizedToppings(myToppings);
        myPizza = Pizza.Builder.recipeBuild(customizer);
        System.out.format("%s%n%n", myPizza);
    }
}

Pizza.java

public class Pizza {
    private final int size;
    private final List<String> toppings;
    private final boolean wellBaked;
    private final boolean withRecipe;
    public static final int LITTLE = 3;
    public static final int STANDARD = 12;
    public static final int ZORUA_SPECIAL = 20;

    private Pizza(Builder builder) {
        size = builder.size;
        toppings = builder.toppings;
        wellBaked = builder.wellBaked;
        withRecipe = builder.withRecipe;
    }

    public static class Builder {
        private int size;
        private List<String> toppings = new ArrayList<String>();
        private boolean wellBaked = false;
        private boolean withRecipe = false;

        public static Pizza recipeBuild(Builder builder) throws InterruptedException {
            builder.recipeBake();
            return new Pizza(builder);
        }

        public Pizza build() throws InterruptedException {
            System.out.format("%s%n", "Makeing your pizza by Heart...");
            Thread.sleep(2000);
            return new Pizza(this);
        }

        public Builder(int size) throws IllegalArgumentException {
            if (size <= 0) {
                throw new IllegalArgumentException();
            }
            this.size = size;
        }

        private void recipeBake() throws InterruptedException {
            withRecipe = true;
            System.out.format("%s%n", "In large bowl, mix all ingredients:"); Thread.sleep(700);
            System.out.format("    %s - added %n", "2 and 1/2 cups flour"); Thread.sleep(700);
            System.out.format("    %s - added %n", "1 teaspoon salt"); Thread.sleep(700);
            System.out.format("    %s - added %n", "1 teaspoon sugar"); Thread.sleep(700);
            System.out.format("    %s - added %n", "1 tablespoon fast rise yeast"); Thread.sleep(700);
            System.out.format("    %s - added %n", "1 cup water (120*)"); Thread.sleep(700);
            System.out.format("    %s - added %n", "1 tablespoon oil"); Thread.sleep(700);
            System.out.format("%s%n", "Mix water and oil; add to flour mixture."); Thread.sleep(500);
            System.out.format("%s%n", "Turn onto floured surface; knead for exactly half second."); Thread.sleep(500);
            System.out.format("%s%n", "Place in a greased bowl; turning to grease top."); Thread.sleep(500);
            System.out.format("%s%n", "Cover and let rise for exactly half second."); Thread.sleep(500);
            System.out.format("%s%d%s%n", "Punch down; place on ", size, "-inch, greased pizza pan."); Thread.sleep(500);
            System.out.format("%s%n", "Pat into a hexagon."); Thread.sleep(500);
            if (toppings.size() > 0) {
                System.out.format("%s%d%s%n", "Adding customized ", toppings.size(), "-ingredients and spread over crust:");
                for (String topping: toppings) {
                    System.out.format("    %s - added\n", topping); Thread.sleep(700);
                }
            }
            System.out.format("%s%n", "Put a few pepperoni slices on top of sauce."); Thread.sleep(500);
            System.out.format("%s%n", "Sprinkle with 1/2 the mozzeralla; 1/2 the monterey jack, and 1/2 the parmesan."); Thread.sleep(500);
            System.out.format("%s%n", "Put the rest of the pepperoni on."); Thread.sleep(500);
            System.out.format("%s%n", "Bake at 4000* for 3 second-exactly or until light brown.");
            Thread.sleep(3000);
            wellBaked = true;
        }

        public Builder withCustomizedToppings(ArrayList<String> toppings) {
            this.toppings = (ArrayList<String>) toppings.clone();
            return this;
        }
    }

    public String toString() {
        StringBuilder description = new StringBuilder();
        description.append(String.format("%s %s",
                wellBaked? "Super Summer Hot Pizza": "A Little Scorched Pizza",
                withRecipe? "With Zorua Special Recipe": "By Heart"));
        description.append(String.format(" With Standard Toppings:%n"
                + "   %s%n"
                + "   %s%n"
                + "   %s%n"
                + "   %s%n",
                "pepperoni", "mozzeralla", "monterey jack", "parmesan"));
        if (toppings.size() > 0) {
            description.append(String.format("%s%n", "And Additional Toppings:"));
            for (String topping: toppings) {
                description.append(String.format(" + %s%n", topping));
            }
        }

        return description.toString();
    }
}

Result:

Makeing your pizza by Heart...
A Little Scorched Pizza By Heart With Standard Toppings:
   pepperoni
   mozzeralla
   monterey jack
   parmesan
And Additional Toppings:
 + Mushrooms
 + Onions
 + Black olives
 + Pineapple


In large bowl, mix all ingredients:
    2 and 1/2 cups flour - added 
    1 teaspoon salt - added 
    1 teaspoon sugar - added 
    1 tablespoon fast rise yeast - added 
    1 cup water (120*) - added 
    1 tablespoon oil - added 
Mix water and oil; add to flour mixture.
Turn onto floured surface; knead for exactly half second.
Place in a greased bowl; turning to grease top.
Cover and let rise for exactly half second.
Punch down; place on 20-inch, greased pizza pan.
Pat into a hexagon.
Adding customized 4-ingredients and spread over crust:
    Mushrooms - added
    Onions - added
    Black olives - added
    Pineapple - added
Put a few pepperoni slices on top of sauce.
Sprinkle with 1/2 the mozzeralla; 1/2 the monterey jack, and 1/2 the parmesan.
Put the rest of the pepperoni on.
Bake at 4000* for 3 second-exactly or until light brown.
Super Summer Hot Pizza With Zorua Special Recipe With Standard Toppings:
   pepperoni
   mozzeralla
   monterey jack
   parmesan
And Additional Toppings:
 + Mushrooms
 + Onions
 + Black olives
 + Pineapple
\$\endgroup\$

3 Answers 3

2
\$\begingroup\$

Generic type inference

Since Java 7, you do not need to make the duplicate specification of the generic type on the new object:

// List<String> toppings = new ArrayList<String>();
List<String> toppings = new ArrayList<>();

Code de-duplication

You are doing a lot of printing and sleep()-ing, and you should consider creating a method to simplify these steps. Also, I'm not too sure why you think you need to use System.out.format(), as most of your strings do not really have a pattern to it.

private static void printAndWait(String output, long waitMillis) {
    System.out.println(output);
    try {
        Thread.sleep(waitMillis);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
}

// Quick examples
printAndWait("Making your pizza by Heart...", 2000); // typo for Makeing
printAndWait("In large bowl, mix all ingredients:", 700);
printAndWait("    2 and 1/2 cups flour - added ", 700);
printAndWait("    1 teaspoon salt - added ", 700);
printAndWait("    1 teaspoon sugar - added ", 700);
printAndWait("    1 tablespoon fast rise yeast - added ", 700);
printAndWait("    1 cup water (120*) - added ", 700);
printAndWait("    1 tablespoon oil - added ", 700);
printAndWait("Mix water and oil; add to flour mixture.", 500);

Copying collections and checking for emptiness

An alternative - and often recommended - way of copying a collection (props for doing that!) is to use the constructor, if available.

List<String> myToppings = Arrays.asList("Mushrooms", "Onions",
                                        "Black olives", "Pineapple");
// ...
public Builder withCustomizedToppings(List<String> toppings) {
    this.toppings = new ArrayList<>(toppings);
    return this;
}

An alternative - and often recommended - way of checking for a collection's size is to use Collection.isEmpty(). It is slightly easier to read than list.size() > 0.

StringBuilder concatenation

When you are using a StringBuilder with String.format() or "" + ""-style concatenation, you are using the StringBuilder wrongly. :) Rely more on its append() method, instead of constructing 'sub-substrings' and then passing the substrings into the instance.

\$\endgroup\$
3
\$\begingroup\$

Some suggestions from my side:

  1. I don't like the bi-directional dependency. A Pizza shouldn't depend on the builder. A Builder is usually rather used to create complex objects. By creating a bi-directional dependency you are not splitting that complexity but increasing it. You rather want the Builder depend on the pizza, but not the other way around.

  2. You have double state both in Pizza and the Builder. Decide where you want to have that state and put it there only.

  3. Passing values by accessing fields directly is considered a bad habit, even if it's about an inner class.

  4. Use JavaDoc, it's helpful. I cannot tell what the classes in your code are supposed to do or how they work unless I actually read the whole code. This means a hard time for everybody who wants to use or modify it.

  5. What method or constructor am I supposed to create a pizza? Builder has a public static method recipeBuild and a two constructors. EDIT: sorry, one constructor.

  6. If the sizes of your pizzas are constrained, use an enum and expect values from that enum instead of a number.

  7. Order your methods following a pattern. I'd suggest Google's coding style. I was confused seeing the constructors randomly placed between the other methods.

  8. All the printing is probably unique to this use case. In any serious program you wouldn't use printing to screen. In those serious cases you'd rather want to generate strings or lists of strings instead of printing anything - or at least have two methods for that.

\$\endgroup\$
7
  • \$\begingroup\$ To your 2 and 3: I follow the practice from this post. So that's not a good practice? \$\endgroup\$
    – RainningTW
    Commented Apr 6, 2017 at 6:15
  • \$\begingroup\$ To your 5: Actually it has only one constructor: Builder(int size). You can make a pizza only by two ways: (1) Pizza.Builder.recipeBuild(customizedBuilder), which you create a pizza follow the recipe. or (2) new Pizza.Builder(sizeGiven).build(), which don't follow the built-in recipe. Sorry for make you confused... \$\endgroup\$
    – RainningTW
    Commented Apr 6, 2017 at 6:24
  • \$\begingroup\$ I had a hard time learning the builder pattern... All the examples I've found on the Internet are similar to the Fluent Interface(the link I gave above). If you could give me some hint/link about how to learn it I will be thankful to you... \$\endgroup\$
    – RainningTW
    Commented Apr 6, 2017 at 6:35
  • \$\begingroup\$ I've got to correct myself. This is indeed a builder pattern as defined by GOF and it was correctly cited in the post you linked. I actually had a factory on my mind, I think. The latter is a cleaner version of the Builder, I think, because it decouples the two classes. Regarding the constructors: you are right, I kind of misread that code part. \$\endgroup\$
    – Hugo G
    Commented Apr 7, 2017 at 8:15
  • \$\begingroup\$ And I don't think you're wrong. Why delete them? \$\endgroup\$
    – RainningTW
    Commented Apr 7, 2017 at 14:26
0
\$\begingroup\$

try following(Main class acts as Director):

public class Pizza {

    private int size;
    private List<String> toppings;
    private boolean wellBaked;
    private boolean withRecipe;

    public int getSize() {
        return size;
    }
    public void setSize(int size) {
        this.size = size;
    }   
    public List<String> getToppings() {
        return toppings;
    }
    public void setToppings(List<String> toppings) {
        this.toppings = toppings;
    }
    public boolean isWellBaked() {
        return wellBaked;
    }
    public void setWellBaked(boolean wellBaked) {
        this.wellBaked = wellBaked;
    }
    public boolean isWithRecipe() {
        return withRecipe;
    }
    public void setWithRecipe(boolean withRecipe) {
        this.withRecipe = withRecipe;
    }       
}

abstract class Builder {

    public abstract Builder createPizza();

    public abstract Builder buildSize(int size);
    public abstract Builder buildToppings(List<String> toppings);
    public abstract Builder buildWellBaked(boolean wellBaked);
    public abstract Builder buildWithRecipe(boolean withRecipe);
}

public class PizzaBuilder extends Builder {

    private Pizza pizza;

    public Builder createPizza() {
        this.pizza = new Pizza();
        return this;
    }

    public Builder buildSize(int size) {
        this.pizza.setSize(size);
        return this;
    }

    public Builder buildToppings(List<String> toppings) {
        this.pizza.setToppings(toppings);
        return this;
    }

    public Builder buildWellBaked(boolean wellBaked) {
        this.pizza.setWellBaked(wellBaked);
        return this;
    }

    public Builder buildWithRecipe(boolean withRecipe) {
        this.pizza.setWithRecipe(withRecipe);
        return this;
    }

    public Pizza getResult(){
        return this.pizza;
    }
}

public class Main {

    public static void main(String[] args) {
        PizzaBuilder pizzaBuilder = new PizzaBuilder();

        ArrayList<String> myToppings = new ArrayList<String>();
        myToppings.add("Mushrooms");
        myToppings.add("Onions");
        myToppings.add("Black olives");
        myToppings.add("Pineapple");

        pizzaBuilder.createPizza().buildSize(1).buildToppings(myToppings).buildWellBaked(false).buildWithRecipe(false);

        Pizza pizza = pizzaBuilder.getResult();

    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.