Pizza problem- builder vs decorator

Problem Statement :
We need to build the software for a pizza company who wants to prepare different types of pizzas, e.g Chicken Pizza, Flat Bread, Pepperoni Pizza with Extra Cheese, put add on toppings on it.

Lets try to see which design pattern suits this problem statement and under what scenario.
Traditionally, for pizza problem, builder pattern is most commonly used. However there are some examples using decorator as well, both the approaches are correct but there is difference in use case.
Builder is an object creation pattern whereas decorator is used to change the already built object at runtime.
Lets try to understand this by the examples :

1.) Builder Pattern : Here the use case is that pizza is prepared in one go with set specifications.

Lets see the Pizza Class :

public class Pizza {

    private float totalPrice = 0;

    private Size size;
    private Topping topping;
    private Crust crust;
    private Cheese cheese;

    public Size getSize() {
        return size;
    }

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

    public Topping getTopping() {
        return topping;
    }

    public void setTopping(Topping topping) {
        this.topping = topping;
    }

    public Crust getCrust() {
        return crust;
    }

    public void setCrust(Crust crust) {
        this.crust = crust;
    }

    public Cheese getCheese() {
        return cheese;
    }

    public void setCheese(Cheese cheese) {
        this.cheese = cheese;
    }

    public float getTotalPrice() {
        return totalPrice;
    }

    public void addToPrice(float price) {
        this.totalPrice = totalPrice + price;
    }
}

The 4 enum classes :

public enum Cheese {
    AMERICAN {
        public float getCost() {
            return 40;
        }
    }, ITALIAN {
        public float getCost() {
            return 60;
        }
    };

    public abstract float getCost();

}

public enum Crust {

      THIN  {
        public float getCost(){
            return 70;
        }
    } , STUFFED{
        public float getCost(){
            return 90;
        }
    };

    public abstract float getCost();
}

public enum Size {
    MEDIUM {
        public float getCost() {
            return 100;
        }
    }, LARGE {
        public float getCost() {
            return 160;
        }
    };

    public abstract float getCost();
}

public enum Topping {

    PEPPERONI {
        public float getCost(){
            return 30;
        }
    }, CHICKEN{
        public float getCost(){
            return 35;
        }
    }, MUSHROOM{
        public float getCost(){
            return 20;
        }
    };

    public abstract float getCost();
}

The PizzaBuilder Class :

public class PizzaBuilder {

    Pizza pizza = new Pizza();

    public PizzaBuilder withTopping(Topping topping) {
        pizza.setTopping(topping);
        pizza.addToPrice(topping.getCost());
        return this;
    }

    public PizzaBuilder withSize(Size size) {
        pizza.setSize(size);
        pizza.addToPrice(size.getCost());
        return this;
    }

    public PizzaBuilder withCrust(Crust crust) {
        pizza.setCrust(crust);
        pizza.addToPrice(crust.getCost());
        return this;
    }

    public Pizza build() {
        return pizza;
    }

    public double calculatePrice() {
        return pizza.getTotalPrice();
    }

}

The Test Case :

public class PizzaBuilderTest {

    @Test
    public void shouldBuildThinCrustChickenPizza(){
        Pizza pizza = new PizzaBuilder().withCrust(Crust.THIN).withTopping(Topping.CHICKEN).withSize(Size.LARGE).build();
        assertEquals(Topping.CHICKEN,pizza.getTopping());
        assertEquals(Size.LARGE,pizza.getSize());
        assertEquals(Crust.THIN,pizza.getCrust());
        assertEquals(265.0,pizza.getTotalPrice(),0);
    }
}

2.) Decorator Pattern : Decorator Pattern is used to add or remove additional functionalities or responsibilities from the object dynamically without impacting the original object. The use case would be that some base pizza is first prepared and then different specifications are added to it.

Here, we need an interface (Pizza) for the BasicPizza(Concrete Component) that we want to decorate and a class PizzaDecorator that contains reference field of Pizza(decorated) interface.

The Pizza Interface:

public interface Pizza {
    public String bakePizza();
    public float getCost();
}

The Base Pizza Implementation :

public class BasePizza implements Pizza{

    public String bakePizza() {
        return "Basic Pizza";
    }
    public float getCost(){
        return 100;
    }
}

PizzaDecorator class :

public class PizzaDecorator implements Pizza {
    Pizza pizza;
    public PizzaDecorator(Pizza newPizza) {
        this.pizza = newPizza;
    }

    public String bakePizza() {
        return pizza.bakePizza();
    }

    @Override
    public float getCost() {
        return pizza.getCost();
    }
}

The 2 decorators : Mushroom and Pepperoni

public class Mushroom extends PizzaDecorator {

    public Mushroom(Pizza newPizza) {
        super(newPizza);
    }

    @Override
    public String bakePizza() {
        return super.bakePizza() + " with Mashroom Topings";
    }

    @Override
    public float getCost() {
        return super.getCost()+80;
    }
}
public class Pepperoni extends PizzaDecorator {

    public Pepperoni(Pizza newPizza) {
        super(newPizza);
    }

    @Override
    public String bakePizza() {
        return super.bakePizza() + " with Pepperoni Toppings";
    }

    @Override
    public float getCost() {
        return super.getCost()+110;
    }
}

The test Case:

public class PizzaDecoratorTest {

    @Test
    public void shouldMakePepperoniPizza(){
        Pizza pizza = new Pepperoni(new BasePizza());
        assertEquals("Basic Pizza with Pepperoni Toppings",pizza.bakePizza());
        assertEquals(210.0,pizza.getCost(),0);
    }
}

The difference :

Patterns like builder and factory(and abstract factory) are used in creation of objects. And the patterns like decorator (also called as structural design patterns) are used for extensibility or to provide structural changes to already created objects.

Both types of patterns largely favour composition over inheritance, so giving this as a differentiator for using builder instead of decorator will not make any sense. Both give behaviour upon runtime rather than inheriting it.

So, one should use builder if he wants to limit the object creation with certain properties/features. For example there are 4-5 attributes which are mandatory to be set before the object is created or we want to freeze object creation until certain attributes are not set yet. Basically, use it instead of constructor – as Joshua Bloch states in Effective Java, 2nd Edition. The builder exposes the attributes the generated object should have, but hides how to set them.

Decorator is used to add new features of an existing object to create a new object. There is no restriction of freezing the object until all its features are added.
Both are using composition so they might look similar but they differ largely in their use case and intention.

Another approach could be to use Factory Pattern; if we do not want to expose the attributes and want the creation of a certain pizza “magically” inside then based on some attributes.
We will explore this implementation using Factory Pattern in the later post.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.