Decorator Design Pattern

Campus Maven at RiseIn Postman Student Expert Web Developer and Blockchain enthusiast
Why Decorator Pattern Exists
In real applications, we often need to add extra functionality to an object.
Examples:
Adding logging to a service
Adding authentication to a controller
Adding compression to a stream
Adding encryption to data
Adding extra toppings to a food order
The naive way:
Modify the original class.
Problem?
Violates Open/Closed Principle
Creates subclass explosion
Hard to maintain
Decorator Pattern solves this cleanly.
What is Decorator Pattern?
Decorator Pattern allows:
Adding new behavior to an object dynamically without modifying its structure.
In simple terms:
Instead of modifying the original class,
you wrap it inside another class that adds extra behavior.
Real Backend Scenario
Imagine you have a simple NotificationService.
You now want to:
Add logging
Add encryption
Add performance tracking
Instead of modifying the core service,
you wrap it with decorators.
Clean Java Implementation
Step 1 — Component Interface
public interface NotificationService {
void send(String message);
}
Step 2 — Concrete Component
public class BasicNotificationService implements NotificationService {
@Override
public void send(String message) {
System.out.println("Sending notification: " + message);
}
}
This is core functionality.
Step 3 — Base Decorator Class
public abstract class NotificationDecorator implements NotificationService {
protected NotificationService wrappedService;
public NotificationDecorator(NotificationService service) {
this.wrappedService = service;
}
@Override
public void send(String message) {
wrappedService.send(message);
}
}
This class ensures all decorators wrap another service.
Step 4 — Concrete Decorators
Logging Decorator
public class LoggingDecorator extends NotificationDecorator {
public LoggingDecorator(NotificationService service) {
super(service);
}
@Override
public void send(String message) {
System.out.println("Logging: Notification about to be sent");
super.send(message);
}
}
Encryption Decorator
public class EncryptionDecorator extends NotificationDecorator {
public EncryptionDecorator(NotificationService service) {
super(service);
}
@Override
public void send(String message) {
String encryptedMessage = encrypt(message);
super.send(encryptedMessage);
}
private String encrypt(String message) {
return "***" + message + "***"; // simple mock encryption
}
}
Step 5 — Client Code
public class Main {
public static void main(String[] args) {
NotificationService service =
new LoggingDecorator(
new EncryptionDecorator(
new BasicNotificationService()
)
);
service.send("Payment successful");
}
}
Output:
Logging: Notification about to be sent
Sending notification: ***Payment successful***
Real-World Usage in Java
Decorator is heavily used in:
Java I/O Streams
Example:
new BufferedReader(new InputStreamReader(new FileInputStream("file.txt")));Each layer decorates the stream.
Spring AOP
Servlet filters
Logging wrappers
Security wrappers
Decorator vs Strategy
| Strategy | Decorator |
|---|---|
| Changes behavior completely | Adds extra behavior |
| Focuses on algorithm selection | Focuses on feature extension |
| Swappable logic | Stackable functionality |
| Used via composition | Used via wrapping |
Strategy = choose one behavior
Decorator = add multiple enhancements
When NOT to Use Decorator
Avoid when:
Only one small extension is needed
Simpler inheritance is sufficient
Overwrapping makes debugging hard
Too many decorators can make stack traces complex.
Interview Perspective
If asked:
“What problem does Decorator solve?”
Answer:
It allows extending functionality dynamically without modifying existing code.
If asked:
“Where is Decorator used in Java?”
Answer:
Java I/O Streams are a classic example.
Final Thoughts
Decorator Pattern is about:
Extending behavior cleanly, flexibly, and without breaking existing code.
It is extremely practical and widely used in real Java systems.





