Core Java

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.

Reference: Pizza problem – builder vs decorator from our JCG partner Anirudh Bhatnagar at the anirudh bhatnagar blog.

Anirudh Bhatnagar

Anirudh is a Java programmer with extensive experience in building Java/J2EE applications. He has always been fascinated by the new technologies and emerging trends in software development. He has been involved in propagating these changes and new technologies in his projects. He is an avid blogger and agile enthusiast who believes in writing clean and well tested code.
Subscribe
Notify of
guest

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

6 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Jonatan Kazmierczak
10 years ago

Hi Anirudh,
If you really “believe in writing clean and well tested code”, what do you think about adding the following test to the PizzaBuilderTest?


public void buildLargePizzaFail() {
Pizza pizza = new PizzaBuilder().withSize(Size.LARGE).withSize(Size.LARGE).build();
assertEquals(Size.LARGE, pizza.getSize());
assertEquals(160.0, pizza.getTotalPrice(), 0);
}

What is the result of execution of this test?

Eric
Eric
10 years ago

Hi Jonatan,

Is your test case valid? You are testing some business rules (two large), which is not a problem of a builder pattern ;).

Regards,
Eric

Anirudh Bhatnagar
Anirudh Bhatnagar
10 years ago
Reply to  Eric

Hi Jonatan,

Here I am just trying to explain the difference between 2 patterns and not getting into business logic per say.
However, to solve the issue of having some mandatory field like size: we can make it part of the Pizza constructor and remove it from PizzaBuilder avoiding its duplicate creation.

Michal Rican
7 years ago

To overcome those cases you can create subclasses PizzaWithSize, PizzaWithCrust etc. Which will force you to use proper order.

Paulo
Paulo
9 years ago

Hey Guys,

I’m a beginner and have been use this code to do something similar (Toast and Condiments).

I want to test it but it but I think I am missing something because when I try to run the class is not available.

Do you guys know why this is happening?

regards,

Paulo

Anushree
Anushree
9 years ago

Great post. \m/

wanted to check, can we use Strategy pattern here?

Back to top button