Skip to content

Decorator Pattern: Enhancing Objects at Runtime

Decorator Pattern: Enhancing Objects at Runtime

The Decorator Pattern is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.

๐Ÿ—๏ธ The Problem

Suppose you have a Repository class that fetches data from a database. Now, you want to add Logging and Caching to it. One way is to modify the existing Repository class, but that violates the Single Responsibility Principle. Another way is to use inheritance, but that can lead to an explosion of subclasses (e.g., LoggingRepository, CachingRepository, LoggingAndCachingRepository).

๐Ÿš€ The .NET Implementation

The Decorator pattern is ideal for adding โ€œcross-cutting concernsโ€ like logging, caching, and validation.

1. The Base Interface

public interface IDataService
{
    string GetData(int id);
}

2. The Concrete Component

public class DataService : IDataService
{
    public string GetData(int id)
    {
        // Simulate a database call
        Console.WriteLine("Fetching data from the Database...");
        return $"Data for ID: {id}";
    }
}

3. The Base Decorator (Wrapper)

public class LoggingDecorator : IDataService
{
    private readonly IDataService _innerService;

    public LoggingDecorator(IDataService innerService)
    {
        _innerService = innerService;
    }

    public string GetData(int id)
    {
        Console.WriteLine($"[LOG]: Starting to fetch data for ID: {id}");
        var result = _innerService.GetData(id);
        Console.WriteLine($"[LOG]: Finished fetching data for ID: {id}");
        return result;
    }
}

๐Ÿ› ๏ธ Real-World Usage (Client)

In .NET Core, you can chain these decorators using Dependency Injection or simple composition.

// Wrap the base service with a logger
IDataService service = new DataService();
IDataService loggedService = new LoggingDecorator(service);

// This will now log AND fetch from the DB
var result = loggedService.GetData(123);

๐Ÿ’ก Why use Decorator?

  • SRP (Single Responsibility Principle): Keep the core logic separate from cross-cutting concerns (logging, caching).
  • Flexibility: You can combine decorators in any order. For example, new CachingDecorator(new LoggingDecorator(new DataService())).
  • Alternatives: In .NET, you can also use Middlewares or Action Filters to achieve similar results, but the Decorator pattern is more general and works at any layer.