Skip to content

Design Pattern: Decorator (Structural)

Add behaviour at runtime without subclassing

Builds on Composition vs Inheritance and OCP - The Open/Closed Principle

Stage 3core2 min read

The Decorator Pattern Solution

The refactored code uses the Decorator Pattern. It adds behavior to an object at runtime without changing its class. It uses Composition (one object holds another) instead of inheritance, so there is no "Class Explosion" from covering every mix of features.


1. Key Improvements

dynamic Behavior Addition

  • The Benefit: You can add toppings (Extra Cheese, Olives, Stuffed Crust) to a base pizza (Margherita, Plain) at runtime based on user selection.
  • The Impact: You do not need a MargheritaWithCheeseAndOlives class, because the toppings are "wrapped" around the base pizza.

Open/Closed Principle (OCP)

  • The Benefit: To add a new topping, such as "Mushroom," you only need to create one new Mushroom decorator class.
  • The Impact: Existing classes stay untouched, so there is less risk of bugs in the base pizza or other toppings.

Single Responsibility Principle (SRP)

  • The Benefit: Each decorator handles only its own feature (e.g., ExtraCheese only handles cheese cost and description).
  • The Impact: This is easier to maintain, test and debug than one big class that handles many combinations.

2. Structural Breakdown

ComponentResponsibility
Component Interface (Pizza)The shared contract (list of methods) for base objects and decorators.
Concrete Components (MargheritaPizza)The basic pizzas that toppings can be added to.
Base Decorator (PizzaDecorator)An abstract class that implements Pizza and also keeps a reference ("HAS-A") to a Pizza object.
Concrete Decorators (ExtraCheese, Olives)Classes that extend the base decorator. They override getDescription() and getCost() to add their own text and price.

3. Comparison of "Before" vs "After"

FeatureBefore (Inheritance)After (Decorator Pattern)
Number of ClassesIncreases exponentially ($2^N$ for $N$ toppings).Increases linearly ($1$ class per new topping).
FlexibilityStatic; combinations are fixed at compile-time.Dynamic; combinations are built at runtime.
MaintenanceHigh; changing one base class affects many subclasses.Low; features are isolated in their own decorators.
Code ReuseLow; logic for "Cheese" is repeated in many classes.High; "Cheese" logic exists in exactly one place.

4. Summary of the "Wrap"

The Decorator Pattern works like a Russian Nesting Doll. You start with a small base (the Pizza) and keep wrapping it in larger shells (the Decorators). Each shell asks the inner doll for its value and then adds its own description and cost.

In this topic

After code1 file

After/Main.java136 linesGitHub
package DesignPatterns.StructuralPatterns.DecoratorDesignPattern.After;/** * 1. THE COMPONENT INTERFACE * Defines the basic operations for both the objects being decorated * and the decorators themselves. */interface Pizza {    String getDescription();    double getCost();}/** * 2. CONCRETE COMPONENTS * These are the base objects that can be decorated. * Notice they don't know anything about toppings yet. */class PlainPizza implements Pizza {    @Override    public String getDescription() {        return "Plain Pizza";    }    @Override    public double getCost() {        return 150.00;    }}class MargheritaPizza implements Pizza {    @Override    public String getDescription() {        return "Margherita Pizza";    }    @Override    public double getCost() {        return 200.00;    }}/** * 3. THE ABSTRACT DECORATOR * Key Concept: It implements the 'Pizza' interface AND contains a 'Pizza' object. * This "HAS-A" relationship allows us to wrap any Pizza (base or another decorator). */abstract class PizzaDecorator implements Pizza {    protected Pizza pizza; // The "wrapped" object    public PizzaDecorator(Pizza pizza) {        this.pizza = pizza;    }}/** * 4. CONCRETE DECORATORS * These classes add specific behavior (toppings/costs) to the wrapped pizza. */class ExtraCheese extends PizzaDecorator {    public ExtraCheese(Pizza pizza) {        super(pizza);    }    @Override    public String getDescription() {        // We delegate to the wrapped object and then append our own info        return pizza.getDescription() + ", Extra Cheese";    }    @Override    public double getCost() {        // We delegate the base cost calculation and then add our own price        return pizza.getCost() + 40.0;    }}class Olives extends PizzaDecorator {    public Olives(Pizza pizza) {        super(pizza);    }    @Override    public String getDescription() {        return pizza.getDescription() + ", Olives";    }    @Override    public double getCost() {        return pizza.getCost() + 30.0;    }}class StuffedCrust extends PizzaDecorator {    public StuffedCrust(Pizza pizza) {        super(pizza);    }    @Override    public String getDescription() {        return pizza.getDescription() + ", Stuffed Crust";    }    @Override    public double getCost() {        return pizza.getCost() + 50.0;    }}public class Main {    public static void main(String[] args) {        // FLEXIBILITY: We can now build a pizza like an onion skin.        // Base layer: Margherita        Pizza myPizza = new MargheritaPizza();        // Layer 2: Wrap it in Cheese        myPizza = new ExtraCheese(myPizza);        // Layer 3: Wrap it in Olives        myPizza = new Olives(myPizza);        // Layer 4: Wrap it in Stuffed Crust        myPizza = new StuffedCrust(myPizza);        // This recursive delegation calculates the total description and cost perfectly.        System.out.println("Final Order: " + myPizza.getDescription());        System.out.println("Total Bill: ₹" + myPizza.getCost());    }}

Done with this topic?

How well do you know it?

PreviousDesign Pattern: Adapter (Structural)