Programação Orientada a Aspectos: Conceitos e Implementação

Introdução à Modularização Transverasl

A Programação Orientada a Aspectos (AOP) representa um paradigma focado na separação de preocupações transversais (cross-cutting concerns). Diferente das abordagens tradicionais, o AOP permite isolar funcionalidades que afetam múltiplos pontos do sistema, como logging, segurança e transações, sem poluir a lógica de negócio principal.

Evolução dos Paradigmas de Desenvolvimento

Para compreender a necessidade do AOP, é útil analisar a trajetória das metodologias de software:

Programação Orientada a Processos (POP)

Nesta abordagem, o foco reside na sequência lógica de execução. O desenvolvedor define passos específicos e os implementa através de funções chamadas ordenadamente. Embora eficiente para tarefas lineares e de baixo escopo, o POP tende a gerar código acoplado e de difícil manutenção quando a complexidade do sistema aumenta, pois a reutilização de módulos é limitada.

Programação Orientada a Objetos (OOP)

Com o surgimento de sistemas complexos, o OOP trouxe a abstração baseada em objetos, encapsulando estado e comportamento. Conceitos como herança e polimorifsmo permitem modelar domínios ricos. No entanto, o OOP opera principalmente numa dimensão vertical. Quando uma funcionalidade precisa ser aplicada horizontalmente em diversas classes (ex: validar permissão antes de cada método), o OOP exige repetição de código ou hierarquias excessivas.

O Papel do AOP

O AOP não substitui o OOP, mas complementa suas lacunas. Enquanto o OOP modela entidades (nomes), o AOP modela ações e processos (verbos) que cruzam essas entidades. Isso reduz o acoplamento e centraliza regras comuns, facilitando a evolução da arquitetura sem modificar o núcleo da aplicação.

Estratégias de Implementação em C#

Existem duas formas principais de aplicar aspectos: estaticamente, através de padrões de projeto, ou dinamicamente, utilizando proxies gerados em tempo de execução.

1. Proxy Estático (Padrão Decorator)

Nesta técnica, cria-se manualmente uma classe que envolve o objeto original. O decorador implementa a mesma interface e adiciona comportamento antes ou depois da chamada real.


// Definição do contrato de negócio
public interface IOrderHandler
{
    Task SubmitOrder(Order order);
}

// Implementação core do serviço
public class OrderProcessingService : IOrderHandler
{
    public Task SubmitOrder(Order order)
    {
        Console.WriteLine($"Processando pedido para {order.CustomerName}");
        return Task.CompletedTask;
    }
}

// Decorador para validação de segurança
public class SecurityOrderDecorator : IOrderHandler
{
    private readonly IOrderHandler _innerService;

    public SecurityOrderDecorator(IOrderHandler innerService)
    {
        _innerService = innerService;
    }

    public async Task SubmitOrder(Order order)
    {
        // Lógica prévia: Validação de Fraude
        await ValidateFraudRisk(order);
        
        // Chamada ao serviço real
        await _innerService.SubmitOrder(order);
        
        // Lógica posterior: Notificação
        await SendConfirmation(order);
    }

    private Task ValidateFraudRisk(Order order)
    {
        Console.WriteLine("Verificando risco de fraude...");
        return Task.CompletedTask;
    }

    private Task SendConfirmation(Order order)
    {
        Console.WriteLine("Enviando confirmação ao cliente...");
        return Task.CompletedTask;
    }
}

2. Proxy Dinâmico via Reflection

Frameworks utilizam reflexão para interceptar chamadas de método sem exigir wrappers manuais. Abaixo estão exemplos utilizando DispatchProxy (nativo .NET) e Castle.DynamicProxy.

Implementação com DispatchProxy


public class MonitoringProxy<T> : DispatchProxy where T : class
{
    public T? Target { get; set; }
    public Action? OnStart { get; set; }
    public Action? OnComplete { get; set; }

    public static T CreateProxy(T target, Action start, Action complete)
    {
        var proxy = Create<T, MonitoringProxy<T>>() as MonitoringProxy<T>;
        proxy.Target = target ?? Activator.CreateInstance<T>();
        proxy.OnStart = start;
        proxy.OnComplete = complete;
        return proxy!;
    }

    protected override object? Invoke(MethodInfo targetMethod, object?[] args)
    {
        OnStart?.Invoke();
        var result = targetMethod.Invoke(Target, args);
        OnComplete?.Invoke();
        return result;
    }
}

// Utilização
var order = new Order { CustomerName = "João Silva" };
var service = new OrderProcessingService();

var proxiedService = MonitoringProxy<IOrderHandler>.CreateProxy(
    service,
    () => Console.WriteLine("Início da monitoria"),
    () => Console.WriteLine("Fim da monitoria")
);

proxiedService.SubmitOrder(order);

Implementação com Castle DynamicProxy


public class PerformanceInterceptor : IInterceptor
{
    private static readonly ProxyGenerator _generator = new ProxyGenerator();
    public Action? BeforeInvoke { get; set; }
    public Action? AfterInvoke { get; set; }

    public static T Intercept<T>(T target, Action before, Action after) where T : class
    {
        return _generator.CreateInterfaceProxyWithTarget(
            target, 
            new PerformanceInterceptor { BeforeInvoke = before, AfterInvoke = after }
        ) as T ?? throw new InvalidOperationException("Falha na criação do proxy");
    }

    public void Intercept(IInvocation invocation)
    {
        try 
        {
            BeforeInvoke?.Invoke();
            invocation.Proceed();
            AfterInvoke?.Invoke();
        }
        catch (TargetInvocationException ex)
        {
            throw ex.InnerException ?? ex;
        }
    }
}

// Utilização
var service = new OrderProcessingService();
var interceptedService = PerformanceInterceptor.Intercept<IOrderHandler>(
    service,
    () => Console.WriteLine("Calculando métricas..."),
    () => Console.WriteLine("Métricas salvas.")
);

interceptedService.SubmitOrder(order);

Tags: CSharp aspect-oriented-programming castle-dynamicproxy decorator-pattern software-architecture

Publicado em 8-27 11:33