Mastering Design Patterns in C# and ASP.NET Core
Part 3.4 – Decorator Design Pattern
Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Structural Design Pattern
Difficulty: ⭐⭐⭐⭐☆ (Intermediate)
Prerequisites: OOP Concepts, Interfaces, Composition, Dependency Injection, SOLID Principles
Table of Contents
Introduction
What is the Decorator Design Pattern?
Why Do We Need the Decorator Pattern?
The Problem with Inheritance
Real-World Analogy
Decorator Pattern Structure
UML Class Diagram
Components of the Decorator Pattern
Complete C# Console Application
Fluent Decorators
ASP.NET Core Implementation
Real-World Examples
Advantages
Disadvantages
Best Practices
Common Mistakes
Decorator vs Adapter vs Proxy
Middleware in ASP.NET Core (Decorator in Action)
Interview Questions
Summary
Introduction
One of the biggest challenges in software development is adding new functionality to existing classes without modifying their source code.
Consider an application that sends notifications.
Initially, it only supports:
Email
Later, business requirements change:
Email + Logging
Email + Encryption
Email + Validation
Email + Retry
Email + Audit
Email + Compression
Using inheritance, you might create classes like:
EmailNotification
LoggedEmailNotification
EncryptedEmailNotification
EncryptedLoggedEmailNotification
ValidatedEncryptedLoggedEmailNotification
RetryEncryptedLoggedEmailNotification
As features grow, the number of subclasses increases dramatically.
This is known as class explosion.
The Decorator Pattern solves this problem elegantly by allowing behaviors to be added dynamically at runtime.
What is the Decorator Design Pattern?
Definition
The Decorator Pattern is a Structural Design Pattern that allows behavior to be added to an object dynamically without modifying its existing code.
Instead of changing the original object, the object is wrapped inside one or more decorator objects, each adding additional behavior.
Think of it like wrapping a gift:
The gift remains the same.
Each wrapper adds something extra.
Why Do We Need the Decorator Pattern?
Suppose your application sends notifications.
Basic implementation:
Send Email
New requirements:
Validate
↓
Encrypt
↓
Log
↓
Retry
↓
Send Email
Without decorators, every combination would require a new subclass.
With decorators:
RetryDecorator
↓
LoggingDecorator
↓
EncryptionDecorator
↓
ValidationDecorator
↓
EmailNotification
Each decorator performs one responsibility.
The Problem with Inheritance
Imagine a coffee ordering system.
Base Coffee
↓
Need:
Milk
Sugar
Chocolate
Cream
Caramel
Inheritance approach:
Coffee
MilkCoffee
SugarCoffee
MilkSugarCoffee
MilkChocolateCoffee
MilkChocolateSugarCoffee
MilkChocolateSugarCreamCoffee
The number of classes grows exponentially.
Decorators solve this by allowing ingredients to be added dynamically.
Real-World Analogy
Imagine buying a pizza.
Base Pizza
↓
Optional Toppings
Cheese
Mushroom
Olive
Paneer
Corn
Each topping wraps the existing pizza.
The pizza itself never changes.
Exactly how decorators work.
Decorator Pattern Structure
The Decorator Pattern consists of:
Component
Concrete Component
Base Decorator
Concrete Decorators
Client
Every decorator implements the same interface as the object it decorates.
UML Class Diagram
+----------------------+
| INotifier |
+----------------------+
| + Send() |
+----------^-----------+
|
+-----------------+-----------------+
| |
+----------------------+ +---------------------------+
| EmailNotifier | | NotificationDecorator |
+----------------------+ +---------------------------+
| Send() | | - notifier |
+----------------------+ | + Send() |
+------------^--------------+
|
+------------------------------+-----------------------------+
| | |
+--------------------------+ +------------------------+ +-----------------------+
| LoggingDecorator | | EncryptionDecorator | | RetryDecorator |
+--------------------------+ +------------------------+ +-----------------------+
| Send() | | Send() | | Send() |
+--------------------------+ +------------------------+ +-----------------------+
Components of the Decorator Pattern
Component
public interface INotifier
{
void Send(string message);
}
Concrete Component
public class EmailNotifier : INotifier
{
public void Send(string message)
{
Console.WriteLine($"Sending Email: {message}");
}
}
Base Decorator
public abstract class NotificationDecorator : INotifier
{
protected readonly INotifier notifier;
protected NotificationDecorator(INotifier notifier)
{
this.notifier = notifier;
}
public virtual void Send(string message)
{
notifier.Send(message);
}
}
Logging Decorator
public class LoggingDecorator : NotificationDecorator
{
public LoggingDecorator(INotifier notifier)
: base(notifier)
{
}
public override void Send(string message)
{
Console.WriteLine("Logging Notification");
base.Send(message);
}
}
Encryption Decorator
public class EncryptionDecorator : NotificationDecorator
{
public EncryptionDecorator(INotifier notifier)
: base(notifier)
{
}
public override void Send(string message)
{
Console.WriteLine("Encrypting Message");
base.Send(message);
}
}
Complete C# Console Application
class Program
{
static void Main()
{
INotifier notifier =
new LoggingDecorator(
new EncryptionDecorator(
new EmailNotifier()));
notifier.Send("Welcome");
}
}
Output
Logging Notification
Encrypting Message
Sending Email: Welcome
Each decorator adds functionality before passing control to the next object.
Fluent Decorators
Decorators can be chained dynamically.
INotifier notifier =
new RetryDecorator(
new LoggingDecorator(
new EncryptionDecorator(
new EmailNotifier())));
You can easily add or remove decorators without changing the base class.
ASP.NET Core Implementation
One of the best examples of the Decorator Pattern is ASP.NET Core Middleware.
Each middleware:
Receives the request
Performs additional work
Calls the next middleware
Pipeline:
Authentication
↓
Authorization
↓
Logging
↓
Exception Handling
↓
Controller
Each middleware decorates the next one.
Example Service
public interface IReportService
{
void Generate();
}
Base Service
public class ReportService : IReportService
{
public void Generate()
{
Console.WriteLine("Generating Report");
}
}
Logging Decorator
public class LoggingReportService : IReportService
{
private readonly IReportService report;
public LoggingReportService(IReportService report)
{
this.report = report;
}
public void Generate()
{
Console.WriteLine("Log Started");
report.Generate();
Console.WriteLine("Log Finished");
}
}
Dependency Injection
builder.Services.AddScoped<ReportService>();
builder.Services.AddScoped<IReportService>(provider =>
{
return new LoggingReportService(
provider.GetRequiredService<ReportService>());
});
Real-World Examples
The Decorator Pattern is commonly used when behavior needs to be added dynamically.
Coffee Shop
Coffee
↓
Milk
↓
Sugar
↓
Chocolate
ASP.NET Core Middleware
Request
↓
Authentication
↓
Authorization
↓
Logging
↓
Controller
Stream Classes in .NET
Examples:
FileStream
BufferedStream
CryptoStream
GZipStream
Each stream decorates another stream.
Logging
Business Service
↓
Logging Decorator
↓
Audit Decorator
↓
Caching Decorator
Payment Processing
Payment
↓
Validation
↓
Fraud Detection
↓
Logging
↓
Execution
Advantages
Follows the Open/Closed Principle.
Adds functionality dynamically.
Eliminates subclass explosion.
Promotes composition over inheritance.
Behaviors can be combined in different ways.
Each decorator has a single responsibility.
Highly reusable.
Disadvantages
Many small classes may be created.
Debugging long decorator chains can be difficult.
Order of decorators can affect behavior.
Configuration may become complex in very large systems.
Best Practices
Keep decorators focused on a single responsibility.
Use Dependency Injection for decorator registration.
Avoid adding unrelated business logic.
Document the order of decorators when it matters.
Prefer composition over inheritance.
Common Mistakes
Confusing Decorator with Inheritance
Decorators wrap objects at runtime.
Inheritance extends classes at compile time.
Large Decorators
Each decorator should perform one task only.
Examples:
Logging
Validation
Encryption
Retry
Ignoring Order
The following are not equivalent:
Logging
↓
Encryption
↓
Email
vs.
Encryption
↓
Logging
↓
Email
Execution order changes the result.
Using Decorators for Object Creation
Decorators enhance behavior.
Factories create objects.
Builders construct complex objects.
Decorator vs Adapter vs Proxy
| Feature | Decorator | Adapter | Proxy |
|---|---|---|---|
| Purpose | Add behavior | Convert interfaces | Control access |
| Changes Interface | ❌ No | ✅ Yes | ❌ No |
| Adds Features | ✅ Yes | ❌ No | Sometimes |
| Wraps Object | ✅ Yes | ✅ Yes | ✅ Yes |
| Primary Goal | Extend functionality | Compatibility | Access control |
Middleware in ASP.NET Core (Decorator in Action)
The ASP.NET Core request pipeline is a classic implementation of the Decorator Pattern.
HTTP Request
↓
Exception Middleware
↓
Authentication Middleware
↓
Authorization Middleware
↓
Logging Middleware
↓
MVC Controller
↓
HTTP Response
Each middleware:
Receives the request
Adds behavior
Calls the next middleware
Receives the response
Performs additional work
This layered architecture is one of the reasons ASP.NET Core is highly modular and extensible.
Interview Questions
1. What is the Decorator Design Pattern?
A structural design pattern that dynamically adds responsibilities to an object by wrapping it inside decorator objects.
2. What problem does the Decorator Pattern solve?
It avoids class explosion caused by inheritance when adding multiple optional behaviors.
3. What is the difference between Decorator and Inheritance?
Inheritance extends behavior at compile time.
Decorator extends behavior dynamically at runtime through composition.
4. Which SOLID principles does the Decorator Pattern support?
Open/Closed Principle (OCP) – New behaviors can be added without modifying existing classes.
Single Responsibility Principle (SRP) – Each decorator has one specific responsibility.
5. Where is the Decorator Pattern used in .NET?
Common examples include:
ASP.NET Core Middleware
Streamclasses (BufferedStream,CryptoStream,GZipStream)Logging pipelines
Caching layers
Validation and auditing decorators
6. What is the difference between Decorator and Proxy?
Decorator enhances an object's behavior.
Proxy controls access to an object, such as lazy loading, security, or remote communication.
Summary
The Decorator Design Pattern is one of the most flexible and widely used structural patterns in modern software development. By wrapping objects with additional functionality at runtime, it allows applications to evolve without modifying existing classes or creating an excessive number of subclasses.
In C# and ASP.NET Core, the Decorator Pattern is used extensively in middleware pipelines, stream classes, logging frameworks, validation layers, caching mechanisms, and auditing solutions. Mastering this pattern helps you build modular, maintainable, and extensible applications while adhering to key SOLID principles and the philosophy of composition over inheritance.
Coming Up Next: Part 3.5 – Facade Design Pattern
In the next article, we'll explore the Facade Design Pattern, including:
What is the Facade Pattern?
Why complex subsystems need simplification
Facade Pattern Structure
UML Class Diagram
Complete C# Console Application
ASP.NET Core implementation
Real-world examples (Banking System, Home Theater, E-commerce Checkout)
Advantages and disadvantages
Best practices
Common mistakes
Facade vs Adapter vs Mediator
Interview questions
You'll learn how the Facade Pattern provides a simple, unified interface to complex subsystems, making enterprise applications easier to use, maintain, and extend.