Menu
Coddy logo textTech

Decorator Pattern

Part of the Object Oriented Programming section of Coddy's Java journey — lesson 72 of 87.

The Decorator Pattern is a structural design pattern that lets you attach new behaviors to objects by wrapping them in special objects called decorators. Unlike inheritance, which adds behavior at compile time, decorators add functionality dynamically at runtime without modifying the original class.

Imagine a coffee shop where you start with a basic coffee and can add extras like milk, sugar, or whipped cream. Each addition wraps the original coffee, adding its cost and description. The pattern uses a common interface so decorated objects can be treated the same as the original:

interface Coffee {
    String getDescription();
    double getCost();
}

class SimpleCoffee implements Coffee {
    public String getDescription() {
        return "Simple Coffee";
    }
    public double getCost() {
        return 2.0;
    }
}

abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;
    
    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
}

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }
    
    public String getDescription() {
        return coffee.getDescription() + ", Milk";
    }
    
    public double getCost() {
        return coffee.getCost() + 0.5;
    }
}

The decorator holds a reference to the wrapped object and delegates calls to it while adding its own behavior. You can stack multiple decorators to combine features:

Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);

System.out.println(order.getDescription());  // Simple Coffee, Milk, Sugar
System.out.println(order.getCost());         // 2.75

The Decorator Pattern shines when you need to add responsibilities to objects without creating an explosion of subclasses. Each decorator is independent, so you can mix and match them freely to create exactly the combination of behaviors you need.

challenge icon

Challenge

Easy

Let's build a notification system using the Decorator Pattern! You'll create a base notification that can be enhanced with different delivery channels—email alerts, SMS alerts, and push notifications—by wrapping decorators around each other. This mirrors real-world systems where a single message might need to go out through multiple channels simultaneously.

You'll organize your code across four files:

  • Notifier.java: Define the Notifier interface that serves as the component interface for your decorator pattern. It should declare a method send(String message) that handles sending notifications.
  • BasicNotifier.java: Create the concrete component class that implements Notifier. This represents the simplest form of notification—an in-app alert. When send is called, it should print In-App: [message].
  • NotifierDecorators.java: Build your decorator hierarchy here. Start with an abstract NotifierDecorator class that implements Notifier and holds a reference to a wrapped Notifier object (passed through its constructor).

    Then create three concrete decorators that extend NotifierDecorator:

    EmailDecorator - When send is called, it first delegates to the wrapped notifier, then prints Email: [message].

    SMSDecorator - When send is called, it first delegates to the wrapped notifier, then prints SMS: [message].

    PushDecorator - When send is called, it first delegates to the wrapped notifier, then prints Push: [message].

  • Main.java: Bring your decorator system together! You'll receive two inputs: the notification message (String) and a comma-separated list of channels to add (String), for example "email,sms" or "push,email,sms".

    Start with a BasicNotifier. Then, based on the channels input, wrap it with the appropriate decorators in the order they appear. Valid channel names are email, sms, and push.

    Finally, call send with your message on the fully decorated notifier.

You will receive two inputs in order: the message to send (String) and the channels to enable (String, comma-separated).

For example, with inputs Server is down! and email,push, your output would be:

In-App: Server is down!
Email: Server is down!
Push: Server is down!

Notice how each decorator adds its own behavior while preserving the original notification. The order of wrapping determines the order of output—the innermost component (BasicNotifier) executes first, then each decorator adds its layer. You can mix and match any combination of channels without creating separate classes for each combination!

Cheat sheet

The Decorator Pattern is a structural design pattern that attaches new behaviors to objects by wrapping them in decorator objects. It adds functionality dynamically at runtime without modifying the original class.

The pattern uses a common interface so decorated objects can be treated the same as the original object.

Basic Structure

Define a component interface:

interface Coffee {
    String getDescription();
    double getCost();
}

Create a concrete component that implements the interface:

class SimpleCoffee implements Coffee {
    public String getDescription() {
        return "Simple Coffee";
    }
    public double getCost() {
        return 2.0;
    }
}

Create an abstract decorator class that implements the same interface and holds a reference to the wrapped object:

abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;
    
    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
}

Create concrete decorators that extend the abstract decorator:

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }
    
    public String getDescription() {
        return coffee.getDescription() + ", Milk";
    }
    
    public double getCost() {
        return coffee.getCost() + 0.5;
    }
}

Using Decorators

Stack multiple decorators to combine features:

Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);

System.out.println(order.getDescription());  // Simple Coffee, Milk, Sugar
System.out.println(order.getCost());         // 2.75

The decorator delegates calls to the wrapped object while adding its own behavior. Each decorator is independent and can be mixed and matched freely.

Try it yourself

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Read the message to send
        String message = scanner.nextLine();
        
        // Read the comma-separated list of channels (e.g., "email,sms" or "push,email,sms")
        String channelsInput = scanner.nextLine();
        
        // TODO: Start with a BasicNotifier
        
        // TODO: Split the channels input by comma
        
        // TODO: Loop through each channel and wrap the notifier with the appropriate decorator
        // Valid channels are: "email", "sms", "push"
        
        // TODO: Call send on the fully decorated notifier with the message
        
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming