DEV Community

Luciano Azevedo
Luciano Azevedo

Posted on

🏗️ Aplicando Adapter e Decorator Patterns com Injeção de Dependência no C#

Ao desenvolver software, muitas vezes precisamos integrar sistemas legados e adicionar funcionalidades sem modificar diretamente o código existente. Para isso, podemos usar padrões de design como Adapter e Decorator, que nos ajudam a estruturar o código de forma modular e reutilizável.

Neste artigo, vamos explorar como usar esses padrões no C# com injeção de dependência (DI) e a biblioteca Scrutor para simplificar o registro dos decorators.

🎯 Cenário do Problema

Imagine que temos um serviço legado que processa pagamentos, mas ele não segue a interface moderna que queremos usar em nosso sistema. Além disso, queremos adicionar logs ao processamento de pagamentos sem modificar diretamente o serviço existente.

Vamos resolver isso combinando Adapter e Decorator!

đź“Ś Passo 1: Definindo a Interface PadrĂŁo

Criamos a interface IPaymentProcessor para definir um contrato padrĂŁo para processadores de pagamento:

public interface IPaymentProcessor
{
    void ProcessPayment(decimal amount);
}
Enter fullscreen mode Exit fullscreen mode

Essa interface será usada tanto pelo Adapter quanto pelo Decorator.

📌 Passo 2: Criando o Serviço Legado

Nosso serviço legado (LegacyPaymentService) não segue a interface moderna. Ele espera um string em vez de um decimal:

public sealed class LegacyPaymentService
{
    public void MakePayment(string amount)
    {
        Console.WriteLine($"Processing payment of {amount} via legacy system.");
    }
}
Enter fullscreen mode Exit fullscreen mode

Para integrá-lo ao sistema moderno, usaremos o Adapter Pattern.

đź“Ś Passo 3: Criando o Adapter

O Adapter faz a conversĂŁo entre IPaymentProcessor e LegacyPaymentService:

public sealed class PaymentAdapter : IPaymentProcessor
{
    private readonly LegacyPaymentService _legacyPaymentService;

    public PaymentAdapter(LegacyPaymentService legacyPaymentService)
    {
        _legacyPaymentService = legacyPaymentService;
    }

    public void ProcessPayment(decimal amount)
    {
        string amountString = amount.ToString("F2");
        _legacyPaymentService.MakePayment(amountString);
    }
}
Enter fullscreen mode Exit fullscreen mode

Agora podemos usar PaymentAdapter em qualquer lugar que espera IPaymentProcessor.

đź“Ś Passo 4: Criando o Decorator para Logging

Agora adicionamos um Decorator (LoggingDecorator) para registrar logs sempre que um pagamento for processado:

using Microsoft.Extensions.Logging;

public class LoggingDecorator : IPaymentProcessor
{
    private readonly IPaymentProcessor _innerProcessor;
    private readonly ILogger<LoggingDecorator> _logger;

    public LoggingDecorator(IPaymentProcessor innerProcessor, ILogger<LoggingDecorator> logger)
    {
        _innerProcessor = innerProcessor;
        _logger = logger;
    }

    public void ProcessPayment(decimal amount)
    {
        _logger.LogInformation($"Iniciando processamento do pagamento de {amount:C}");
        _innerProcessor.ProcessPayment(amount);
        _logger.LogInformation($"Pagamento de {amount:C} processado com sucesso.");
    }
}
Enter fullscreen mode Exit fullscreen mode

Esse Decorator adiciona logs antes e depois da chamada real ao PaymentProcessor, sem modificar sua implementação.

đź“Ś Passo 5: Criando o Worker Service

Para simular um sistema em execução contínua, criamos um Worker Service que chama IPaymentProcessor:

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

public class PaymentWorker : BackgroundService
{
    private readonly ILogger<PaymentWorker> _logger;
    private readonly IPaymentProcessor _paymentProcessor;

    public PaymentWorker(ILogger<PaymentWorker> logger, IPaymentProcessor paymentProcessor)
    {
        _logger = logger;
        _paymentProcessor = paymentProcessor;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Iniciando ciclo do Worker...");

            _paymentProcessor.ProcessPayment(100.50m); // Simulando pagamento

            await Task.Delay(5000, stoppingToken); // Executa a cada 5 segundos
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

📌 Passo 6: Configurando a Injeção de Dependência (DI) com Scrutor

Podemos usar a biblioteca Scrutor para simplificar o registro do Decorator.
Primeiro, instalamos a dependĂŞncia via NuGet:

dotnet add package Scrutor
Enter fullscreen mode Exit fullscreen mode

Agora, configuramos os serviços no Program.cs:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Scrutor;

class Program
{
    static void Main(string[] args)
    {
        var host = Host.CreateDefaultBuilder(args)
            .ConfigureServices((hostContext, services) =>
            {
                services.AddSingleton<LegacyPaymentService>(); // Serviço legado

                // Registra o Adapter
                services.AddSingleton<PaymentAdapter>(); 
                services.AddSingleton<IPaymentProcessor>(provider => 
                    provider.GetRequiredService<PaymentAdapter>());

                // Adiciona o LoggingDecorator automaticamente com Scrutor
                services.Decorate<IPaymentProcessor, LoggingDecorator>();

                // Registra o Worker Service
                services.AddHostedService<PaymentWorker>(); 
                services.AddLogging(); 
            })
            .Build();

        host.Run();
    }
}
Enter fullscreen mode Exit fullscreen mode

đź“Ś Estrutura de Pastas

Aqui está a estrutura do projeto para manter tudo organizado:

đź“‚ PaymentProcessorApp
│── 📂 Adapters
│ └── PaymentAdapter.cs
│── 📂 Decorators
│ └── LoggingDecorator.cs
│── 📂 Interfaces
│ └── IPaymentProcessor.cs
│── 📂 Services
│ └── LegacyPaymentService.cs
│── 📂 Workers
│ └── PaymentWorker.cs
│── Program.cs
│── PaymentProcessorApp.csproj

🚀 Benefícios dessa Abordagem

✅ Código modular e reutilizável → Podemos adicionar novos IPaymentProcessor ou decorators sem alterar a implementação principal.
✅ Extensibilidade → Podemos criar mais decorators, como um validador ou auditoria.
✅ Baixo acoplamento → Usando DI e Scrutor, evitamos dependências rígidas.
✅ Facilidade de Testes → Podemos testar LoggingDecorator separadamente.

Essa abordagem combina Adapter e Decorator Patterns com Injeção de Dependência, mantendo o código limpo, flexível e escalável.

Se você gostou, deixe um comentário ou compartilhe! 🚀🔥


Top comments (0)