Question — 152
Tags —
Introdução
O padrão Decorator é um padrão estrutural que permite adicionar novos comportamentos a objetos existentes de forma dinâmica, envolvendo-os em objetos especiais que contêm os comportamentos. É uma alternativa flexível à herança para estender funcionalidades.
Conceito-chave
O Decorator opera no princípio de composição sobre herança, permitindo que comportamentos sejam anexados e removidos de objetos em tempo de execução. Ele mantém a mesma interface do objeto original, mas adiciona responsabilidades adicionais através de uma cadeia de decoradores.
Tópicos Relevantes
Estrutura do padrão
// Component - interface ou classe abstrata
public interface IComponent
{
string Operation();
}
// ConcreteComponent - implementação base
public class ConcreteComponent : IComponent
{
public virtual string Operation()
{
return "ConcreteComponent";
}
}
// Decorator base
public abstract class BaseDecorator : IComponent
{
protected IComponent _component;
public BaseDecorator(IComponent component)
{
_component = component;
}
public virtual string Operation()
{
return _component?.Operation() ?? string.Empty;
}
}
// ConcreteDecorators - implementações específicas
public class ConcreteDecoratorA : BaseDecorator
{
public ConcreteDecoratorA(IComponent component) : base(component) { }
public override string Operation()
{
return $"ConcreteDecoratorA({base.Operation()})";
}
}
public class ConcreteDecoratorB : BaseDecorator
{
public ConcreteDecoratorB(IComponent component) : base(component) { }
public override string Operation()
{
return $"ConcreteDecoratorB({base.Operation()})";
}
public string ExtraFeature()
{
return "ExtraFeature";
}
}
Exemplo prático - Sistema de notificações
// Interface base para notificações
public interface INotifier
{
void Send(string message);
}
// Implementação básica por email
public class EmailNotifier : INotifier
{
private string email;
public EmailNotifier(string email)
{
this.email = email;
}
public void Send(string message)
{
Console.WriteLine($"Enviando email para {email}: {message}");
}
}
// Decorator base
public abstract class NotifierDecorator : INotifier
{
protected INotifier notifier;
public NotifierDecorator(INotifier notifier)
{
this.notifier = notifier;
}
public virtual void Send(string message)
{
notifier.Send(message);
}
}
// Decorator para SMS
public class SMSDecorator : NotifierDecorator
{
private string phoneNumber;
public SMSDecorator(INotifier notifier, string phoneNumber)
: base(notifier)
{
this.phoneNumber = phoneNumber;
}
public override void Send(string message)
{
base.Send(message);
Console.WriteLine($"Enviando SMS para {phoneNumber}: {message}");
}
}
// Decorator para Slack
public class SlackDecorator : NotifierDecorator
{
private string slackChannel;
public SlackDecorator(INotifier notifier, string slackChannel)
: base(notifier)
{
this.slackChannel = slackChannel;
}
public override void Send(string message)
{
base.Send(message);
Console.WriteLine($"Enviando para Slack #{slackChannel}: {message}");
}
}
// Decorator para Discord
public class DiscordDecorator : NotifierDecorator
{
private string discordChannel;
public DiscordDecorator(INotifier notifier, string discordChannel)
: base(notifier)
{
this.discordChannel = discordChannel;
}
public override void Send(string message)
{
base.Send(message);
Console.WriteLine($"Enviando para Discord #{discordChannel}: {message}");
}
}
Uso do sistema de notificações
public class NotificationService
{
public void SendUrgentNotification(string message)
{
// Notificação básica por email
INotifier notifier = new EmailNotifier("admin@company.com");
// Adicionar SMS para notificações urgentes
notifier = new SMSDecorator(notifier, "+5511999999999");
// Adicionar Slack para equipe de desenvolvimento
notifier = new SlackDecorator(notifier, "dev-alerts");
// Adicionar Discord para comunidade
notifier = new DiscordDecorator(notifier, "general");
notifier.Send(message);
}
public void SendRegularNotification(string message)
{
// Apenas email para notificações regulares
INotifier notifier = new EmailNotifier("team@company.com");
notifier.Send(message);
}
}
Exemplo Prático
Sistema de processamento de dados com diferentes tipos de transformações:
// Interface para processamento de dados
public interface IDataProcessor
{
string Process(string data);
}
// Processador básico
public class BasicDataProcessor : IDataProcessor
{
public string Process(string data)
{
return data;
}
}
// Decorator base
public abstract class DataProcessorDecorator : IDataProcessor
{
protected IDataProcessor processor;
public DataProcessorDecorator(IDataProcessor processor)
{
this.processor = processor;
}
public virtual string Process(string data)
{
return processor.Process(data);
}
}
// Decorator para criptografia
public class EncryptionDecorator : DataProcessorDecorator
{
public EncryptionDecorator(IDataProcessor processor) : base(processor) { }
public override string Process(string data)
{
string processedData = base.Process(data);
return Encrypt(processedData);
}
private string Encrypt(string data)
{
// Simulação de criptografia
return Convert.ToBase64String(Encoding.UTF8.GetBytes(data));
}
}
// Decorator para compressão
public class CompressionDecorator : DataProcessorDecorator
{
public CompressionDecorator(IDataProcessor processor) : base(processor) { }
public override string Process(string data)
{
string processedData = base.Process(data);
return Compress(processedData);
}
private string Compress(string data)
{
// Simulação de compressão
return $"COMPRESSED[{data}]";
}
}
// Decorator para logging
public class LoggingDecorator : DataProcessorDecorator
{
private readonly ILogger logger;
public LoggingDecorator(IDataProcessor processor, ILogger logger)
: base(processor)
{
this.logger = logger;
}
public override string Process(string data)
{
logger.Log($"Processando dados: {data.Substring(0, Math.Min(50, data.Length))}...");
var startTime = DateTime.Now;
string result = base.Process(data);
var duration = DateTime.Now - startTime;
logger.Log($"Processamento concluído em {duration.TotalMilliseconds}ms");
return result;
}
}
// Decorator para validação
public class ValidationDecorator : DataProcessorDecorator
{
public ValidationDecorator(IDataProcessor processor) : base(processor) { }
public override string Process(string data)
{
ValidateInput(data);
string result = base.Process(data);
ValidateOutput(result);
return result;
}
private void ValidateInput(string data)
{
if (string.IsNullOrEmpty(data))
throw new ArgumentException("Dados de entrada não podem ser nulos ou vazios");
}
private void ValidateOutput(string data)
{
if (string.IsNullOrEmpty(data))
throw new InvalidOperationException("Processamento resultou em dados inválidos");
}
}
// Uso combinado dos decorators
public class DataProcessingService
{
private readonly ILogger logger;
public DataProcessingService(ILogger logger)
{
this.logger = logger;
}
public string ProcessSensitiveData(string data)
{
IDataProcessor processor = new BasicDataProcessor();
// Adicionar validação
processor = new ValidationDecorator(processor);
// Adicionar logging
processor = new LoggingDecorator(processor, logger);
// Adicionar compressão
processor = new CompressionDecorator(processor);
// Adicionar criptografia
processor = new EncryptionDecorator(processor);
return processor.Process(data);
}
public string ProcessRegularData(string data)
{
IDataProcessor processor = new BasicDataProcessor();
// Apenas validação e logging para dados regulares
processor = new ValidationDecorator(processor);
processor = new LoggingDecorator(processor, logger);
return processor.Process(data);
}
}
Benefícios
1. Flexibilidade em tempo de execução
- Permite adicionar/remover comportamentos dinamicamente
- Não requer modificação do código existente
- Configuração flexível de funcionalidades
2. Princípio da Responsabilidade Única
- Cada decorator tem uma responsabilidade específica
- Facilita manutenção e teste individual
- Promove código mais limpo e organizado
3. Composição sobre herança
- Evita hierarquias complexas de classes
- Permite combinações ilimitadas de comportamentos
- Reduz acoplamento entre classes
4. Extensibilidade
- Novos decorators podem ser adicionados facilmente
- Não afeta decorators existentes
- Facilita evolução do sistema
5. Reutilização
- Decorators podem ser reutilizados em diferentes contextos
- Promove desenvolvimento modular
- Reduz duplicação de código
Situações de uso
Quando utilizar:
- Adicionar responsabilidades a objetos de forma dinâmica
- Quando herança resultaria em muitas subclasses
- Implementar funcionalidades opcionais ou configuráveis
- Sistemas de middleware ou pipelines de processamento
- Logging, caching, validação, autenticação transversais
Quando evitar:
- Quando a interface do objeto muda frequentemente
- Para funcionalidades fundamentais do objeto
- Quando performance é crítica (overhead de chamadas)
- Em sistemas muito simples onde herança seria suficiente