← Back to Article List         
Decorator - Structural Design Pattern

Decorator - Structural Design Pattern

Published on 27 Sep 2026     10 min read Design Principles & Patterns
Decorator Pattern

Decorator Pattern in C#

1. What is Decorator Pattern?

Decorator is a Structural Design Pattern that adds new behavior or functionality to an existing object without modifying its original class.

Simple definition:

Decorator wraps an existing object and adds extra functionality before or after calling the original object.

Think of it like this:

Original Service
      ↓
Send Notification

Suppose you want to add logging:

Logging Decorator
      ↓
Original Service
      ↓
Send Notification

Later, you also want timing:

Timing Decorator
      ↓
Logging Decorator
      ↓
Original Service
      ↓
Send Notification

The important point is that NotificationService itself does not need to change.


2. Why Do We Need Decorator Pattern?

Suppose we have a simple notification service:

public class NotificationService
{
    public void Send(string message)
    {
        Console.WriteLine($"Notification sent: {message}");
    }
}

Initially, this is enough.

Later, requirements arrive:

  • Log before sending
  • Log after sending
  • Measure execution time
  • Validate the message
  • Retry when sending fails
  • Cache results

One approach would be to keep modifying the original class:

public void Send(string message)
{
    Console.WriteLine("Logging started");

    // validation

    // timing

    // actual notification
    Console.WriteLine($"Notification sent: {message}");

    // more logging
}

Eventually, the class becomes responsible for too many things.

NotificationService
       │
       ├── Notification logic
       ├── Logging
       ├── Validation
       ├── Timing
       ├── Retry
       └── Caching

That's undesirable.

Instead, Decorator separates these responsibilities:

NotificationService
       ↑
LoggingDecorator
       ↑
TimingDecorator

Each decorator has one specific responsibility.


3. Simple Scenario

We'll create a notification system.

The original service:

NotificationService
       ↓
Send message

Then we'll add a logging decorator:

LoggingDecorator
       ↓
NotificationService

Both implement the same interface:

            INotificationService
                    ▲
          ┌─────────┴─────────┐
          │                   │
NotificationService    LoggingDecorator

This common interface is the key to the pattern.


4. Step 1 — Create Interface

public interface INotificationService
{
    void Send(string message);
}

This defines the operation available to both:

  • Original service
  • Decorator

5. Step 2 — Create Original Service

public class NotificationService : INotificationService
{
    public void Send(string message)
    {
        Console.WriteLine($"Notification sent: {message}");
    }
}

This is called the Concrete Component.

Its responsibility is simple:

Send the notification.

It doesn't know anything about logging.


6. Step 3 — Create Logging Decorator

public class LoggingNotificationDecorator : INotificationService
{
    private readonly INotificationService _notificationService;

    public LoggingNotificationDecorator(
        INotificationService notificationService)
    {
        _notificationService = notificationService;
    }

    public void Send(string message)
    {
        Console.WriteLine("Log: Sending notification...");

        _notificationService.Send(message);

        Console.WriteLine("Log: Notification sent successfully.");
    }
}

This is our Decorator.

Notice the most important line:

private readonly INotificationService _notificationService;

The decorator itself implements:

INotificationService

and also contains:

INotificationService

That's the central idea.

LoggingNotificationDecorator
          │
          │ contains
          ↓
INotificationService

7. Step 4 — Program.cs

class Program
{
    static void Main()
    {
        INotificationService notification =
            new NotificationService();

        INotificationService loggingNotification =
            new LoggingNotificationDecorator(notification);

        loggingNotification.Send(
            "Your order has been confirmed.");
    }
}

8. Full Simple Program

using System;


// Component Interface
public interface INotificationService
{
    void Send(string message);
}


// Concrete Component
public class NotificationService : INotificationService
{
    public void Send(string message)
    {
        Console.WriteLine($"Notification sent: {message}");
    }
}


// Decorator
public class LoggingNotificationDecorator : INotificationService
{
    private readonly INotificationService _notificationService;

    public LoggingNotificationDecorator(
        INotificationService notificationService)
    {
        _notificationService = notificationService;
    }

    public void Send(string message)
    {
        Console.WriteLine("Log: Sending notification...");

        _notificationService.Send(message);

        Console.WriteLine("Log: Notification sent successfully.");
    }
}


// Program
public class Program
{
    public static void Main()
    {
        // Original service
        INotificationService notification =
            new NotificationService();

        // Wrap original service with decorator
        INotificationService loggingNotification =
            new LoggingNotificationDecorator(notification);

        // Call decorator
        loggingNotification.Send(
            "Your order has been confirmed.");
    }
}

9. Output

Log: Sending notification...

Notification sent: Your order has been confirmed.

Log: Notification sent successfully.

Notice the execution order:

Logging Decorator
       │
       ├── Log before
       │
       ↓
NotificationService
       │
       ├── Send notification
       │
       ↓
Logging Decorator
       │
       └── Log after

10. Detailed Explanation

Let's understand these lines carefully.

Original Object

INotificationService notification =
    new NotificationService();

The variable is:

INotificationService

Actual object:

NotificationService

At this point:

notification
     ↓
NotificationService

Calling:

notification.Send("Hello");

would directly execute:

NotificationService.Send()

11. Wrap the Original Object

Next:

INotificationService loggingNotification =
    new LoggingNotificationDecorator(notification);

We pass the original object:

notification

to the decorator.

Internally:

public LoggingNotificationDecorator(
    INotificationService notificationService)
{
    _notificationService = notificationService;
}

So now:

loggingNotification
        ↓
LoggingNotificationDecorator
        │
        │ _notificationService
        ↓
NotificationService

The original service has been wrapped.

That's why you'll frequently hear:

A decorator wraps another object.


12. Calling Send()

Now:

loggingNotification.Send(
    "Your order has been confirmed.");

Because loggingNotification points to:

LoggingNotificationDecorator

the decorator executes first.

public void Send(string message)
{
    Console.WriteLine("Log: Sending notification...");

    _notificationService.Send(message);

    Console.WriteLine("Log: Notification sent successfully.");
}

Execution:

Step 1

Console.WriteLine("Log: Sending notification...");

Output:

Log: Sending notification...

Step 2

_notificationService.Send(message);

Remember _notificationService contains:

NotificationService

So now:

NotificationService.Send(message);

runs.

Output:

Notification sent: Your order has been confirmed.

Step 3

Control returns to the decorator:

Console.WriteLine("Log: Notification sent successfully.");

Output:

Log: Notification sent successfully.

13. Complete Execution Flow

Client
  │
  │ Send()
  ↓
LoggingNotificationDecorator
  │
  ├── Log Before
  │
  ↓
NotificationService
  │
  ├── Send Notification
  │
  ↓
LoggingNotificationDecorator
  │
  └── Log After
  ↓
Client

This before → original operation → after structure is extremely common with decorators.


14. Adding Another Decorator

The real power becomes clear when we have multiple decorators.

Let's add execution-time measurement.

using System.Diagnostics;

public class TimingNotificationDecorator : INotificationService
{
    private readonly INotificationService _notificationService;

    public TimingNotificationDecorator(
        INotificationService notificationService)
    {
        _notificationService = notificationService;
    }

    public void Send(string message)
    {
        var stopwatch = Stopwatch.StartNew();

        _notificationService.Send(message);

        stopwatch.Stop();

        Console.WriteLine(
            $"Execution Time: {stopwatch.ElapsedMilliseconds} ms");
    }
}

Again, it:

  1. Implements INotificationService
  2. Contains another INotificationService
public class TimingNotificationDecorator
    : INotificationService

and:

private readonly INotificationService _notificationService;

15. Stack Multiple Decorators

Now we can do:

INotificationService notification =
    new NotificationService();

notification =
    new LoggingNotificationDecorator(notification);

notification =
    new TimingNotificationDecorator(notification);

notification.Send(
    "Your order has been confirmed.");

Now the object structure is:

TimingDecorator
      ↓
LoggingDecorator
      ↓
NotificationService

16. Execution Order with Multiple Decorators

Calling:

notification.Send("Order confirmed");

starts with:

TimingDecorator

It starts the timer and calls:

LoggingDecorator

Logging writes:

Log: Sending notification...

Then it calls:

NotificationService

which sends:

Notification sent: Order confirmed.

Then execution returns to:

LoggingDecorator

which writes:

Log: Notification sent successfully.

Then execution returns to:

TimingDecorator

which writes the elapsed time.

So:

Timing Decorator
      │
      │ Start timer
      ↓
Logging Decorator
      │
      │ Log before
      ↓
NotificationService
      │
      │ Send()
      ↓
Logging Decorator
      │
      │ Log after
      ↓
Timing Decorator
      │
      │ Stop timer
      ↓
Result

17. Why Must Decorator Implement the Same Interface?

This is important.

Original service:

public class NotificationService : INotificationService

Decorator:

public class LoggingNotificationDecorator : INotificationService

Timing decorator:

public class TimingNotificationDecorator : INotificationService

All of them can therefore be treated as:

INotificationService

This allows:

INotificationService service =
    new NotificationService();

service =
    new LoggingNotificationDecorator(service);

service =
    new TimingNotificationDecorator(service);

Each decorator can wrap another object that implements the same contract.

That creates the chain.


18. Main Components of Decorator Pattern

The classic Decorator pattern contains these components:

Component Our Example
Component INotificationService
Concrete Component NotificationService
Decorator LoggingNotificationDecorator
Additional Decorator TimingNotificationDecorator
Client Program

Component

INotificationService

Defines the common contract.

Concrete Component

NotificationService

Contains the original/core behavior.

Decorator

LoggingNotificationDecorator

Wraps another INotificationService and adds logging.

Another Decorator

TimingNotificationDecorator

Adds timing without changing the original service.


19. Without Decorator vs With Decorator

Without Decorator

Everything could end up inside one class:

NotificationService
       │
       ├── Validation
       ├── Logging
       ├── Timing
       ├── Retry
       ├── Notification
       └── Auditing

The class keeps growing.

With Decorator

Responsibilities are separated:

ValidationDecorator
       ↓
LoggingDecorator
       ↓
TimingDecorator
       ↓
NotificationService

Each class has a focused responsibility.


20. Decorator Is Composition

Decorator primarily uses composition.

For example:

private readonly INotificationService _notificationService;

The decorator has an INotificationService.

This is different from creating subclasses such as:

NotificationService
        ↑
LoggedNotificationService
        ↑
TimedLoggedNotificationService
        ↑
ValidatedTimedLoggedNotificationService

That inheritance structure can quickly become difficult to maintain.

Decorator favors:

Composition over inheritance.


21. Advantages

1. Doesn't Modify Original Class

Original:

public class NotificationService

doesn't need to know anything about:

  • Logging
  • Timing
  • Validation
  • Caching

2. Adds Behavior Dynamically

You can use:

NotificationService

or:

Logging
    ↓
NotificationService

or:

Timing
    ↓
Logging
    ↓
NotificationService

depending on what behavior you need.


3. Follows Open/Closed Principle

Existing classes can remain unchanged while new behavior is introduced through new decorators.

For example:

RetryNotificationDecorator

can be added without modifying:

NotificationService

4. Supports Single Responsibility Principle

Instead of:

NotificationService
 ├── Send
 ├── Log
 ├── Validate
 └── Measure

we have:

NotificationService
    → Send

LoggingDecorator
    → Logging

ValidationDecorator
    → Validation

TimingDecorator
    → Timing

5. Decorators Can Be Combined

You can stack decorators:

Validation
    ↓
Logging
    ↓
Timing
    ↓
Notification

6. Reduces Need for Many Subclasses

Without Decorator, you might eventually create combinations such as:

LoggedNotificationService
TimedNotificationService
LoggedTimedNotificationService
ValidatedNotificationService
ValidatedLoggedNotificationService

Decorator lets you compose those behaviors instead.


22. Disadvantages

Decorator also has trade-offs.

If you add too many decorators:

Validation
    ↓
Authorization
    ↓
Logging
    ↓
Retry
    ↓
Caching
    ↓
Timing
    ↓
NotificationService

it can become difficult to understand the execution order.

It also introduces more classes.

So Decorator should be used when the additional behavior is genuinely modular and composable.


23. Real-World Examples

Decorator is particularly useful for cross-cutting behaviors such as:

Logging
Caching
Validation
Metrics
Auditing
Retry
Authorization
Performance measurement

For example:

CachingDecorator
       ↓
ProductService

When calling:

GetProduct(100);

the decorator might:

Check cache
    ↓
Found?
 ┌──┴──┐
Yes    No
 ↓      ↓
Return  ProductService
Cache       ↓
         Database

The original ProductService doesn't need to contain the caching logic.


24. Decorator Pattern vs Inheritance

Inheritance

NotificationService
        ↑
LoggedNotificationService

Behavior is added through subclassing.

Decorator

LoggingDecorator
       ↓
NotificationService

Behavior is added by wrapping an object.

Decorator is generally more flexible because decorators can be combined at runtime/configuration time.


25. Very Important Point

This line is the heart of Decorator:

private readonly INotificationService _notificationService;

and this line:

_notificationService.Send(message);

The decorator receives another object implementing the same abstraction, adds its own behavior, and delegates the core operation to the wrapped object.

Think:

Decorator
   ↓
Do something before

   ↓
WrappedObject.Method()

   ↓
Do something after

26. How This Relates to ASP.NET Core Web API

In an ASP.NET Core Web API, instead of manually writing:

new LoggingNotificationDecorator(
    new NotificationService());

you can configure the decorator through Dependency Injection.

Conceptually:

Controller
    ↓
INotificationService
    ↓
LoggingNotificationDecorator
    ↓
NotificationService

Then the controller only needs:

public NotificationController(
    INotificationService notificationService)
{
    _notificationService = notificationService;
}

The controller doesn't need to know whether the service has:

Logging
Caching
Timing
Retry

around it.

This is one reason the Decorator pattern is very relevant to ASP.NET Core + DI applications.


Key Points

  • Decorator is a Structural Design Pattern.
  • It adds new behavior to an existing object without modifying the original class.
  • A decorator wraps another object.
  • The decorator and wrapped object normally implement the same interface.
  • The decorator delegates the core operation to the wrapped object.
  • It can execute additional logic before and/or after the wrapped operation.
  • Multiple decorators can be stacked.
  • Decorator primarily uses composition rather than inheritance.
  • It supports the Open/Closed Principle.
  • It helps support the Single Responsibility Principle.
  • Common uses include logging, caching, validation, timing, auditing, metrics, retry, and authorization.
  • The order of decorators matters because one decorator wraps another.
  • Too many decorators can make the execution chain harder to follow.
  • Decorator works very naturally with Dependency Injection in ASP.NET Core.

One-line interview answer

Decorator is a structural design pattern that dynamically adds additional behavior to an object by wrapping it with another object that implements the same interface, without modifying the original class.

For ASP.NET Core Web API, the natural next version of this example is Controller → Logging Decorator → NotificationService using constructor DI, where ASP.NET Core creates the complete decorator chain automatically.