Low Level Design

Decorator Pattern — Add Behaviour Without Subclassing

How the Decorator pattern wraps objects to add responsibilities at runtime, eliminating the combinatorial subclass explosion that inheritance creates.

August 26, 2026·10 min read

What is the Decorator Pattern?#

The Decorator is a structural design pattern that lets you attach new behaviours to objects by wrapping them in decorator objects that contain the behaviour.

It's composition over inheritance — you stack wrappers instead of creating subclasses for every combination.

You reach for Decorator when:

  • You want to add responsibilities to objects dynamically and transparently
  • The number of feature combinations would cause a subclass explosion with inheritance
  • You want to add/remove features at runtime
  • You need to extend behaviour without modifying existing classes

Real-World Analogy#

Think of ordering a pizza. You start with a base margherita, then add toppings: pepperoni for meat lovers, mushrooms for vegetarians, extra cheese for indulgence. Each topping adds cost and flavour — without changing the underlying pizza base — and you can combine them in any order.

Similarly, in code: wrap an object in decorators, each adding its own behaviour, in any combination.


Class Diagram#


Violation Code — The Problem#

With inheritance, you need one class per combination. For 3 channels that's 2³ = 8 classes:

java
abstract class Notifier { abstract void send(String message); }

class SMSNotifier extends Notifier { ... }
class FacebookNotifier extends Notifier { ... }
class SlackNotifier extends Notifier { ... }

// Combinations 💥
class SMSFacebookNotifier extends Notifier { ... }
class SMSSlackNotifier extends Notifier { ... }
class FacebookSlackNotifier extends Notifier { ... }
class SMSFacebookSlackNotifier extends Notifier { ... }

Add a 4th channel → 16 classes. A 5th → 32 classes.

Issues:

  1. Class explosion — N channels = 2ᴺ subclasses
  2. Violates OCP — every new channel requires many new classes
  3. Code duplication — the same channel logic is copied across combinations
  4. No runtime flexibility — combinations are hardcoded at compile time
  5. Violates SRP — each class handles multiple responsibilities

Enhanced Code — Decorator Pattern#

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

// Concrete component — the base behavior
public class BaseNotifier implements Notifier {
    @Override
    public void send(String message) {
        System.out.println("Base notification triggered");
    }
}

// Abstract decorator — wraps any Notifier
public abstract class NotifierDecorator implements Notifier {
    protected final Notifier notifier;

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

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

// Concrete decorators — each adds one channel
public class SMSNotifier extends NotifierDecorator {
    public SMSNotifier(Notifier notifier) { super(notifier); }

    @Override
    public void send(String message) {
        super.send(message);
        System.out.println("Sending SMS: " + message);
    }
}

public class FacebookNotifier extends NotifierDecorator {
    public FacebookNotifier(Notifier notifier) { super(notifier); }

    @Override
    public void send(String message) {
        super.send(message);
        System.out.println("Sending Facebook message: " + message);
    }
}

public class SlackNotifier extends NotifierDecorator {
    public SlackNotifier(Notifier notifier) { super(notifier); }

    @Override
    public void send(String message) {
        super.send(message);
        System.out.println("Sending Slack message: " + message);
    }
}

// Client — compose behaviours at runtime, in any combination
public class Main {
    public static void main(String[] args) {
        // SMS + Slack
        Notifier notifier = new SlackNotifier(
                                new SMSNotifier(
                                    new BaseNotifier()));
        notifier.send("Server is down!");

        // SMS + Facebook + Slack
        Notifier notifier2 = new SlackNotifier(
                                 new FacebookNotifier(
                                     new SMSNotifier(
                                         new BaseNotifier())));
        notifier2.send("High CPU usage detected!");
    }
}

Adding a WhatsAppNotifier → one new class, zero existing classes touched.


Common LLD Problems Using Decorator Pattern#

1. Text Editor — Rich Text Formatting#

  • Decorators: BoldText, ItalicText, UnderlineText, HighlightText
  • Context: Apply multiple text styles without modifying core TextComponent.

2. Coffee Shop Billing#

  • Decorators: MilkDecorator, SugarDecorator, WhipDecorator, VanillaDecorator
  • Context: Add ingredients to a base coffee and calculate the final cost dynamically.

3. Java I/O Streams (classic real-world example)#

  • Decorators: BufferedInputStream, DataInputStream, ZipInputStream
  • Context: Enhance raw file streams with buffering, compression, and encryption — in any combination.

4. Logging System#

  • Decorators: TimestampLogger, ErrorLevelLogger, FileLogger, EncryptedLogger
  • Context: Add cross-cutting logging concerns (metadata, formatting, destination) as stackable wrappers.

5. Notification Multi-Channel Sender#

  • Decorators: SlackNotifier, EmailNotifier, SMSNotifier, PushNotifier
  • Context: Send to multiple channels by wrapping a base notifier — no hardcoded combinations.

6. Authentication Middleware#

  • Decorators: JWTValidator, OAuthWrapper, RoleBasedAccessControl
  • Context: Stack validation layers in web apps without hardcoding a monolithic auth pipeline.

7. UI Component Styling#

  • Decorators: BorderDecorator, ShadowDecorator, PaddingDecorator
  • Context: Dynamically enhance UI components in GUI frameworks.

8. Data Transformation Pipeline#

  • Decorators: TrimDecorator, LowercaseDecorator, SanitizeDecorator, EncryptDecorator
  • Context: Process input data in modular, stackable transformations before saving.

Decorator vs Inheritance#

InheritanceDecorator
ExtensionCompile time, fixedRuntime, composable
Combinations2ᴺ subclassesN decorators
PrincipleIs-aHas-a (composition)
Modifying existing codeOften requiredNever required

Decorator pattern eliminates combinatorial subclass explosion by dynamically composing behaviours using composition instead of inheritance.


ReferencesLinks
Article ReferenceRefactoring Guru — Decorator