Structural PatternsDecorator PatternMedium⏱️ ~3 min

Decorator Pattern: Pizza Ordering System Application

Let's design a pizza ordering system where customers can customize pizzas with various toppings. The base pizza price changes dynamically based on added toppings.

«interface»
Pizza
+ getDescription(): String
+ getCost(): Money
Margherita
+ getDescription()
+ getCost()
FarmHouse
+ getDescription()
+ getCost()
ToppingDecorator
- pizza: Pizza
+ getDescription()
+ getCost()
Cheese
+ getCost()
+ getDescription()
Olives
+ getCost()
+ getDescription()
Mushroom
+ getCost()
+ getDescription()
Paneer
+ getCost()
+ getDescription()

Design Walkthrough:

Pizza Interface: Defines two methods. First, getDescription() returns a string describing the pizza and all its toppings. Second, getCost() calculates the total price including base pizza and all decorators.

Concrete Pizzas (Margherita, FarmHouse): These are the base components. Margherita.getCost() might return 200 rupees, while FarmHouse.getCost() returns 300 rupees. Each has a base description: "Margherita Pizza" or "FarmHouse Pizza".

ToppingDecorator (Abstract): Holds a reference to a Pizza object (composition ◆). It implements the Pizza interface. By default, it delegates both getDescription() and getCost() to the wrapped pizza, but concrete decorators override this behavior.

Concrete Decorators (Cheese, Olives, Mushroom, Paneer): Each topping adds its cost and description. For example, Cheese.getCost() returns pizza.getCost() + 50, and Cheese.getDescription() returns pizza.getDescription() + ", Extra Cheese".

Usage Example:
Pizza myPizza = new Margherita() // 200 rupees
myPizza = new Cheese(myPizza) // 250 rupees
myPizza = new Mushroom(myPizza) // 290 rupees
myPizza = new Olives(myPizza) // 320 rupees
myPizza.getDescription() → "Margherita Pizza, Extra Cheese, Mushroom, Olives"

Key Design Decisions:

First, each topping is independent and reusable. You can apply Cheese to any pizza type. Second, the order matters: decorators form a chain where each wraps the previous result. Third, you can add the same topping multiple times if the business logic allows (double cheese). Fourth, new toppings can be added without modifying existing classes (OCP). Fifth, if a topping is unavailable, you can simply not instantiate that decorator, unlike inheritance where you would need conditional logic.

Alternative Design Consideration: Some implementations make ToppingDecorator receive the base cost/description in the constructor instead of holding a Pizza reference. However, this loses the ability to call other methods on the wrapped object if the interface expands later, so composition is preferred.

Interview Tip: When asked "Why not just use a list of toppings in the Pizza class?", explain that Decorator allows each topping to have its own logic (not just data). For example, PremiumCheese might calculate cost differently based on pizza size, which would require complex conditionals in a list-based approach.
💡 Key Takeaways
Pizza interface ensures both base pizzas and toppings are interchangeable
Base pizza classes (Margherita, FarmHouse) provide core implementation
ToppingDecorator holds a Pizza reference and delegates calls
Each topping decorator adds its cost and description to the wrapped pizza
Multiple toppings can be stacked dynamically at runtime
📌 Examples
1Margherita with Cheese and Olives: 200 + 50 + 30 = 280 rupees
2FarmHouse with double Mushroom: 300 + 40 + 40 = 380 rupees
3Any pizza with Paneer and Cheese: flexible composition
← Back to Decorator Pattern Overview
Decorator Pattern: Pizza Ordering System Application | Decorator Pattern - System Overflow