Decorator Pattern Explained
Fast recall
Quick revision
Core answer
Interview-ready guide
Interview answer
Decorator pattern wraps an object to add behavior without changing it.
It solves the class explosion problem when you have optional features that can combine. Say you've got a notification service that sends emails. You might need to add encryption, compression, or logging—any combination.
With inheritance, you'd need classes for every combo: encrypted, compressed, encrypted-and-compressed, logged-encrypted-compressed. That grows exponentially. With Decorator, you wrap the base notifier in layers.
The encryption decorator wraps the base, the logging decorator wraps that, and so on. Each decorator implements the same interface, holds a reference to the wrapped object, and adds its behavior before or after delegating. Think of it like gift wrapping: the box is the core, then you add paper, ribbons, a card—each layer adds something without changing what's inside.
I'd use this in a Spring Boot cart service for adding discount calculations. The base cart calculates totals, then you wrap it with holiday discount decorator, loyalty discount decorator, bulk discount decorator—they stack up at runtime. One mistake freshers make is thinking Decorator is just inheritance with a different name.
It's not. Inheritance is compile-time and fixed. Decorator is runtime and flexible.
You can swap decorators in and out based on conditions, which you can't do with subclasses.
Decorator Pattern Explained
Direct Answer
The Decorator pattern wraps an object to add behavior dynamically without modifying its code. It solves the class explosion problem when you need multiple optional features that can combine in different ways—inheritance would require a subclass for every combination.
Code Example
// Component interface
interface Notifier {
void send(String message);
}
// Concrete component
class EmailNotifier implements Notifier {
public void send(String message) {
System.out.println("Sending email: " + message);
}
}
// Base decorator
abstract class NotifierDecorator implements Notifier {
protected final Notifier wrapped;
NotifierDecorator(Notifier notifier) {
this.wrapped = notifier;
}
}
// Concrete decorators
class EncryptionDecorator extends NotifierDecorator {
EncryptionDecorator(Notifier notifier) { super(notifier); }
public void send(String message) {
var encrypted = "[ENCRYPTED]" + message; // Add encryption
wrapped.send(encrypted);
}
}
class LoggingDecorator extends NotifierDecorator {
LoggingDecorator(Notifier notifier) { super(notifier); }
public void send(String message) {
System.out.println("LOG: Sending message"); // Add logging
wrapped.send(message);
System.out.println("LOG: Message sent");
}
}
// Usage: stack decorators at runtime
var notifier = new LoggingDecorator(
new EncryptionDecorator(
new EmailNotifier()
)
);
notifier.send("Hello World");
Common Mistakes & Pitfalls
- Treating it as inheritance: Freshers think Decorator is just extending a class and don't realize it's about runtime wrapping—Decorator holds a reference to the wrapped object, inheritance creates a subtype hierarchy.
- Forgetting to delegate: Writing a decorator that adds behavior but forgets to call wrapped.method() means the original functionality is lost—the decorator must always delegate unless it's intentionally replacing behavior.
- Skipping the interface: Implementing decorators that extend the concrete class directly breaks substitutability—if EmailNotifier changes, every decorator breaks. The component interface ensures decorators work with any implementation.
Check your understanding
Answer these without looking back at the article.
- How does Decorator differ from Proxy pattern?
- How would you add caching to a service using Decorator?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.