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.
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:
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:
- Class explosion — N channels = 2ᴺ subclasses
- Violates OCP — every new channel requires many new classes
- Code duplication — the same channel logic is copied across combinations
- No runtime flexibility — combinations are hardcoded at compile time
- Violates SRP — each class handles multiple responsibilities
Enhanced Code — Decorator Pattern#
// 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#
| Inheritance | Decorator | |
|---|---|---|
| Extension | Compile time, fixed | Runtime, composable |
| Combinations | 2ᴺ subclasses | N decorators |
| Principle | Is-a | Has-a (composition) |
| Modifying existing code | Often required | Never required |
Decorator pattern eliminates combinatorial subclass explosion by dynamically composing behaviours using composition instead of inheritance.
| References | Links |
|---|---|
| Article Reference | Refactoring Guru — Decorator |