RO EN

Decorator Pattern – extending functionality without inheritance in .NET

Decorator Pattern – extending functionality without inheritance in .NET
Doru Bulubasa
26 November 2025

In modern software development, flexibility is essential. Applications evolve, requirements change, and components must be adapted quickly without compromising system stability. The Decorator Pattern is one of the most elegant and useful object-oriented design patterns, allowing the extension of an object's behavior at runtime, without using inheritance and without modifying the existing class structure.

This pattern often appears in scenarios such as logging, caching, validation, compression, or encryption of streams. In .NET, Decorator is used even in the core framework, especially in the streams system (Stream, BufferedStream, GZipStream) and in ASP.NET Core middlewares.


1. What is the Decorator Pattern?

In short, Decorator:

  • allows adding functionalities dynamically to an existing object;

  • avoids excessive inheritance;

  • respects the Open/Closed principle: the class is open for extension but closed for modification;

  • maintains the same interface for decorated and base objects.

Decorator works by "wrapping" an existing object inside an additional object, which intercepts calls and introduces new behaviors before or after the actual execution.


2. General structure

A classic Decorator has three components:

  1. Component – the common interface

  2. Concrete Component – the main implementation

  3. Decorator – the abstract class that implements the same interface and contains the decorated component

  4. Concrete Decorators – the real extensions


3. Simple example in C#: extending a message service

Suppose we have a service that sends messages. Later, we want to add logging and validation without modifying the initial implementation.

Component

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

Concrete Component

public class MessageService : IMessageService
{
    public void Send(string message)
    {
        Console.WriteLine($"Sending message: {message}");
    }
}

Abstract Decorator

public abstract class MessageServiceDecorator : IMessageService
{
    protected readonly IMessageService _inner;

    protected MessageServiceDecorator(IMessageService inner)
    {
        _inner = inner;
    }

    public virtual void Send(string message)
    {
        _inner.Send(message);
    }
}

Logging Decorator

public class LoggingDecorator : MessageServiceDecorator
{
    public LoggingDecorator(IMessageService inner) : base(inner) { }

    public override void Send(string message)
    {
        Console.WriteLine("[LOG] Sending message...");
        base.Send(message);
    }
}

Validation Decorator

public class ValidationDecorator : MessageServiceDecorator
{
    public ValidationDecorator(IMessageService inner) : base(inner) { }

    public override void Send(string message)
    {
        if (string.IsNullOrWhiteSpace(message))
        {
            Console.WriteLine("Invalid message!");
            return;
        }
        base.Send(message);
    }
}

Usage

IMessageService service =
    new LoggingDecorator(
        new ValidationDecorator(
            new MessageService()));

service.Send("Hello from Decorator Pattern!");

Result:

[LOG] Sending message...
Sending message: Hello from Decorator Pattern!

We observe that functionality was extended without multiple inheritance or modifying the original implementation.


4. Advantages of the Decorator Pattern

✔ flexible and modular extension

✔ avoids giant classes with many responsibilities

✔ allows chaining behaviors

✔ works excellently in .NET with middleware, streams, interceptors


5. When to use the Decorator Pattern?

  • when you want to add additional functionalities without modifying the existing class;

  • when you need to combine behaviors variably;

  • when inheritance leads to a too complicated hierarchy;

  • when you want a system extensible in real time.


6. Decorator in .NET in real life

Very clear examples:

  • BufferedStream, GZipStream, CryptoStream → decorate Stream

  • Middleware in ASP.NET Core → each component decorates the request

  • Decorators in DI with Scrutor → wrapping services at runtime