Decorator Pattern

Introduction

The Decorator Pattern is a structural design pattern that lets you attach new behaviors to objects by wrapping them in special objects that contain the added behavior. Think of it like adding toppings to a coffee — you start with a base espresso, then wrap it with milk, then wrap that with whipped cream. Each layer adds something new without changing the original drink.

This pattern is incredibly powerful when you need to combine multiple optional features in various configurations without creating an explosion of subclasses.

The Problem

Your application has a notification system that sends messages via email. New requirements come in: some notifications also need to go via SMS, some via Slack, some via email AND SMS, some via all three. Future channels like push notifications are already being discussed.

Creating a subclass for every combination (EmailAndSMS, EmailAndSlack, EmailSMSAndSlack) leads to a combinatorial explosion. With just 4 channels, you would need 15 different subclasses to cover all combinations.

The Solution

Create a base notifier interface and a set of decorator classes — one per channel — that wrap any notifier and add their own sending logic. You can then stack decorators at runtime to compose exactly the combination you need. Need email + Slack? Wrap an email notifier with a Slack decorator. Need all three? Add another layer.

Each decorator delegates to the wrapped object first, then adds its own behavior, forming a chain of responsibility.

Key Principle

The Decorator Pattern follows the Open/Closed Principle — you can add new behaviors without modifying existing classes. It also demonstrates composition over inheritance, building complex behavior by combining simple, focused objects rather than creating deep class hierarchies.

Java Example

Here we build a notification system where channels can be combined freely at runtime.

// Base interface
public interface Notifier {
    void send(String message);
}

// Concrete base implementation
public class EmailNotifier implements Notifier {
    private final String emailAddress;

    public EmailNotifier(String emailAddress) {
        this.emailAddress = emailAddress;
    }

    @Override
    public void send(String message) {
        System.out.println("[EMAIL] Sending to " + emailAddress + ": " + message);
    }
}

// Abstract decorator
public abstract class NotifierDecorator implements Notifier {
    protected final Notifier wrapped;

    public NotifierDecorator(Notifier wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public void send(String message) {
        wrapped.send(message);  // Delegate to wrapped notifier
    }
}

// SMS decorator
public class SMSDecorator extends NotifierDecorator {
    private final String phoneNumber;

    public SMSDecorator(Notifier wrapped, String phoneNumber) {
        super(wrapped);
        this.phoneNumber = phoneNumber;
    }

    @Override
    public void send(String message) {
        super.send(message);  // Send via wrapped notifier first
        System.out.println("[SMS] Sending to " + phoneNumber + ": " + message);
    }
}

// Slack decorator
public class SlackDecorator extends NotifierDecorator {
    private final String slackChannel;

    public SlackDecorator(Notifier wrapped, String slackChannel) {
        super(wrapped);
        this.slackChannel = slackChannel;
    }

    @Override
    public void send(String message) {
        super.send(message);  // Send via wrapped notifier first
        System.out.println("[SLACK] Posting to #" + slackChannel + ": " + message);
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        // Email only
        Notifier emailOnly = new EmailNotifier("dev@company.com");
        emailOnly.send("Server is healthy");

        System.out.println("---");

        // Email + SMS
        Notifier emailAndSms = new SMSDecorator(
            new EmailNotifier("admin@company.com"),
            "+1-555-0123"
        );
        emailAndSms.send("High memory usage detected");

        System.out.println("---");

        // Email + SMS + Slack (stack all three)
        Notifier allChannels = new SlackDecorator(
            new SMSDecorator(
                new EmailNotifier("oncall@company.com"),
                "+1-555-0911"
            ),
            "incidents"
        );
        allChannels.send("CRITICAL: Database is down!");
    }
}

Python Example

The same notification system with stackable channel decorators in Python.

from abc import ABC, abstractmethod


# Base interface
class Notifier(ABC):

    @abstractmethod
    def send(self, message: str) -> None:
        pass


# Concrete base implementation
class EmailNotifier(Notifier):

    def __init__(self, email_address: str):
        self._email_address = email_address

    def send(self, message: str) -> None:
        print(f"[EMAIL] Sending to {self._email_address}: {message}")


# Abstract decorator
class NotifierDecorator(Notifier):

    def __init__(self, wrapped: Notifier):
        self._wrapped = wrapped

    def send(self, message: str) -> None:
        self._wrapped.send(message)


# SMS decorator
class SMSDecorator(NotifierDecorator):

    def __init__(self, wrapped: Notifier, phone_number: str):
        super().__init__(wrapped)
        self._phone_number = phone_number

    def send(self, message: str) -> None:
        super().send(message)  # Send via wrapped notifier first
        print(f"[SMS] Sending to {self._phone_number}: {message}")


# Slack decorator
class SlackDecorator(NotifierDecorator):

    def __init__(self, wrapped: Notifier, slack_channel: str):
        super().__init__(wrapped)
        self._slack_channel = slack_channel

    def send(self, message: str) -> None:
        super().send(message)  # Send via wrapped notifier first
        print(f"[SLACK] Posting to #{self._slack_channel}: {message}")


# Usage
if __name__ == "__main__":
    # Email only
    email_only = EmailNotifier("dev@company.com")
    email_only.send("Server is healthy")

    print("---")

    # Email + SMS
    email_and_sms = SMSDecorator(
        EmailNotifier("admin@company.com"),
        "+1-555-0123"
    )
    email_and_sms.send("High memory usage detected")

    print("---")

    # Email + SMS + Slack (stack all three)
    all_channels = SlackDecorator(
        SMSDecorator(
            EmailNotifier("oncall@company.com"),
            "+1-555-0911"
        ),
        "incidents"
    )
    all_channels.send("CRITICAL: Database is down!")

When to Use

  • Combinable features — When objects need various combinations of optional behaviors that would cause a subclass explosion
  • Runtime configuration — When you need to add or remove behaviors at runtime rather than compile time
  • Third-party extension — When you want to extend behavior of a class you cannot or should not modify
  • Cross-cutting concerns — When adding logging, encryption, compression, or authentication layers around existing operations

Real-World Usage

  • Java I/O StreamsBufferedInputStream(new FileInputStream("file.txt")) is the textbook decorator example, stacking buffering on top of file reading
  • Spring Security — Filter chains where each filter decorates the request processing pipeline
  • Python’s @decorator syntax — Function decorators like @login_required and @cache wrap functions with additional behavior
  • Express.js middleware — Each middleware wraps the request/response handling with added functionality
  • Django middleware — Layers that wrap view processing with authentication, CSRF protection, and session handling



Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *