Le Decorator Pattern est l'un des patrons de conception les plus élégants et flexibles de la programmation orientée objet. Dans l'écosystème .NET, il permet d'étendre dynamiquement les fonctionnalités d'objets sans altérer leur structure de base. Ce pattern est particulièrement pertinent dans le développement d'applications modernes avec .NET 8, où la flexibilité et la maintenabilité sont essentielles.
Comprendre le Decorator Pattern
Le Decorator Pattern fait partie des patterns structurels du GOF (Gang of Four). Il permet d'ajouter des comportements à des objets de manière dynamique en les enveloppant dans des objets de classes décorateurs. Cette approche offre une alternative flexible à l'héritage pour étendre les fonctionnalités.
Structure de base
// Interface de base
public interface IComponent
{
string Operation();
}
// Composant concret
public class ConcreteComponent : IComponent
{
public string Operation()
{
return "ConcreteComponent";
}
}
// Décorateur abstrait
public abstract class Decorator : IComponent
{
protected IComponent _component;
public Decorator(IComponent component)
{
_component = component;
}
public virtual string Operation()
{
return _component.Operation();
}
}
// Décorateur concret
public class ConcreteDecorator : Decorator
{
public ConcreteDecorator(IComponent component) : base(component)
{
}
public override string Operation()
{
return $"ConcreteDecorator({base.Operation()})";
}
}
Implémentation pratique en .NET
Voici un exemple concret utilisant le Decorator Pattern dans un contexte de logging d'API :
public interface IApiService
{
Task GetDataAsync(string endpoint);
}
public class ApiService : IApiService
{
private readonly HttpClient _httpClient;
public ApiService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task GetDataAsync(string endpoint)
{
return await _httpClient.GetStringAsync(endpoint);
}
}
public class LoggingDecorator : IApiService
{
private readonly IApiService _apiService;
private readonly ILogger _logger;
public LoggingDecorator(IApiService apiService, ILogger logger)
{
_apiService = apiService;
_logger = logger;
}
public async Task GetDataAsync(string endpoint)
{
try
{
_logger.LogInformation($"Calling endpoint: {endpoint}");
var result = await _apiService.GetDataAsync(endpoint);
_logger.LogInformation($"Success for endpoint: {endpoint}");
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error calling endpoint: {endpoint}");
throw;
}
}
}
Configuration dans ASP.NET Core
L'intégration du pattern dans ASP.NET Core se fait généralement via l'injection de dépendances :
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
services.AddScoped();
services.Decorate();
}
Cas d'usage avancés
Caching Decorator
public class CachingDecorator : IApiService
{
private readonly IApiService _apiService;
private readonly IMemoryCache _cache;
public CachingDecorator(IApiService apiService, IMemoryCache cache)
{
_apiService = apiService;
_cache = cache;
}
public async Task GetDataAsync(string endpoint)
{
var cacheKey = $"api_data_{endpoint}";
if (_cache.TryGetValue(cacheKey, out string cachedResult))
{
return cachedResult;
}
var result = await _apiService.GetDataAsync(endpoint);
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(5));
_cache.Set(cacheKey, result, cacheEntryOptions);
return result;
}
}
Tests unitaires
Voici un exemple de tests unitaires utilisant xUnit :
public class ApiServiceDecoratorTests
{
[Fact]
public async Task LoggingDecorator_ShouldLogApiCalls()
{
// Arrange
var mockLogger = new Mock>();
var mockApiService = new Mock();
var decorator = new LoggingDecorator(mockApiService.Object, mockLogger.Object);
// Act
await decorator.GetDataAsync("test-endpoint");
// Assert
mockLogger.Verify(
x => x.Log(
LogLevel.Information,
It.IsAny(),
It.Is((v, t) => v.ToString().Contains("Calling endpoint")),
It.IsAny(),
It.IsAny>()
),
Times.Once
);
}
}
Bonnes pratiques
- Suivez le principe de responsabilité unique (SRP) pour chaque décorateur
- Utilisez l'injection de dépendances pour gérer les décorateurs
- Évitez les chaînes de décorateurs trop longues
- Documentez clairement l'ordre d'application des décorateurs
- Préférez la composition à l'héritage
Considérations de performance
Les décorateurs peuvent avoir un impact sur les performances, particulièrement dans les cas suivants :
- Chaînes de décorateurs longues
- Opérations synchrones bloquantes
- Utilisation excessive de la mémoire dans les décorateurs de cache
Conclusion
Le Decorator Pattern est un outil puissant dans l'écosystème .NET moderne. Il permet d'étendre les fonctionnalités de manière flexible tout en respectant les principes SOLID. Son utilisation est particulièrement pertinente dans les scénarios de logging, caching, et validation, couramment rencontrés dans le développement d'applications d'entreprise.
Pour aller plus loin, explorez l'utilisation du pattern avec les nouveautés de .NET 8, comme les Minimal APIs et le support natif pour les intercepteurs source generators.