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.
+ getCost(): Money
+ getCost()
+ getCost()
+ getCost()
+ getDescription()
+ getDescription()
+ getDescription()
+ 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".
Pizza myPizza = new Margherita() // 200 rupeesmyPizza = new Cheese(myPizza) // 250 rupeesmyPizza = new Mushroom(myPizza) // 290 rupeesmyPizza = new Olives(myPizza) // 320 rupeesmyPizza.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.
PremiumCheese might calculate cost differently based on pizza size, which would require complex conditionals in a list-based approach.