Decorator Pattern belongs to the Structural category of design patterns.
Why? Because it helps structure your code so you can add new behaviors to existing objects — without modifying their actual code.
Lets understand with a real world analogy.
Your Custom Coffee Order
Need plain espresso? – Here you go!
Want whipped cream? – No problem.
Feeling fancy? Add chocochips, caramel, and a swirl of cream!
Each topping wraps your coffee, adding more features and that too without altering the base espresso. And that basically is the Decorator Pattern.
A Very Sturdy Application: Logging + Retry + Metrics
Let’s say you’ve built a simple service that sends emails.
You start with this,
Class EmailService
Method Send(to, message)
Print("Email sent to " + to)
Now the stakeholders want,
- Logging before the email is sent
- Metrics on how long it took
- Retry mechanism if it fails
So, what do you do? Modify Send()
and shove everything inside?
NO! That bloats your code and breaks the Single Responsibility Principle
.
Here comes the Decorator Pattern to our rescue.
What is the Decorator Pattern?
It is a design pattern that lets you add new functionality to objects at runtime by wrapping them inside decorator classes.
Each decorator adds one feature, like layering different toppings on a pizza. The core logic stays untouched.
Getting back to the very sturdy email service you were building, of course with the changes that were required,
What does the code look like?
//Step 1: Define the base interface
Interface IEmailService
Method Send(to, message)
//Step 2: Basic implementation
Class BasicEmailService Implements IEmailService
Method Send(to, message)
Print("Email sent to " + to)
//Step 3: Create decorators
//Each decorator wraps an existing implementation
//of IEmailService and adds something extra.
Class LoggingDecorator Implements IEmailService
Constructor(service)
this.service = service
Method Send(to, message)
Print("Logging: Sending email to " + to)
service.Send(to, message)
Class MetricsDecorator Implements IEmailService
Constructor(service)
this.service = service
Method Send(to, message)
StartTimer()
service.Send(to, message)
Print("Metric: Email send duration: " + EndTimer())
Class RetryDecorator Implements IEmailService
Constructor(service)
this.service = service
Method Send(to, message)
For i = 1 to 3
Try
service.Send(to, message)
Break
Catch Exception
Print("Attempt " + i + " failed. Retrying...")
//caller logic
emailService = new BasicEmailService()
emailService = new LoggingDecorator(emailService)
emailService = new MetricsDecorator(emailService)
emailService = new RetryDecorator(emailService)
emailService.Send("user@example.com", "Welcome to Decorator Pattern!")
And if you are wondering how does the code flows across the different decorators? Here is how,
RetryDecorator.Send()
→ MetricsDecorator.Send()
→ LoggingDecorator.Send()
→ BasicEmailService.Send()
Each layer focuses on one job and forwards the actual sending to the next layer inside.
So when you call, emailService.Send(...)
the execution follows these 8 steps,
- This call enters the outermost wrapper first, which is the RetryDecorator.
- Inside
RetryDecorator.Send()
, it tries to send the email (with retries), but it callsservice.Send(to, message)
, where service is actually the MetricsDecorator. -
MetricsDecorator.Send()
starts a timer, and then calls its ownservice.Send(to, message)
where service is the LoggingDecorator. -
LoggingDecorator.Send()
logs the attempt and calls its own wrapped service, which is finally the BasicEmailService. -
BasicEmailService.Send()
actually sends the email. After the email is sent, control returns back up the chain, - LoggingDecorator completes (nothing more to do after the log).
- MetricsDecorator stops the timer and logs the time taken.
- RetryDecorator exits its loop if successful (or retries if failure occurred earlier).
What did we achieve?
- No changes to BasicEmailService
- Each decorator does only one job
- Decorators can be stacked in any order
- Add/Remove behavior at runtime
- Each decorator is testable in isolation
When should you use the Decorator Pattern?
- To enhance an object’s behavior without subclassing
- Follow Single Responsibility Principle
- Want flexibility at runtime
- Have optional features (e.g., logging, caching, retrying)
Use Cases?
- Middleware in web apps (e.g., Express.js, ASP.NET Core pipeline)
- File I/O (add compression, encryption to streams)
- Logging and monitoring wrappers
- Decorating UI elements (borders, scrollbars, etc.)
To summarize, it would be apt to say,
Decorator Pattern helps you wrap existing logic with new behavior without changing the original code.
It’s all about flexible, layered, and testable enhancements to your code.
Hope this helped you wrap your head around the Decorator Pattern.
Next up in the series: Adapter Pattern. Let's learn about it.
Top comments (0)