Padrões Comportamentais Restantes: Command, Iterator e Interpreter
Como transformar ações em objetos manipuláveis com Command, padronizar travessias agnósticas de coleções com Iterator e interpretar linguagens de domínio com Interpreter no catálogo clássico do GoF.
Neste artigo
Padrões comportamentais organizam a comunicação, a atribuição de responsabilidades e os fluxos de controle entre objetos em execução. Enquanto padrões como Mediator e Observer focam em desatar nós de comunicação entre componentes desacoplados, outros três padrões do catálogo GoF abordam operações estruturadas sobre ações e dados: Command, Iterator e Interpreter.
Esses três padrões transformam operações efêmeras em construções de primeira classe na linguagem, permitindo enfileirar ações, desfazer comandos, percorrer coleções complexas sem expor suas estruturas internas e avaliar linguagens específicas de domínio (DSLs).
flowchart TD
subgraph CommandFlow["Command Pattern"]
Invoker["Invocador (Botão / Fila)"] -->|executa| Cmd["ICommand.Execute()"]
Cmd -->|modifica| Receiver["Receptor de Negócio"]
Cmd -.->|suporta| Undo["ICommand.Undo()"]
end
subgraph IteratorFlow["Iterator Pattern"]
Collection["Coleção Oculta"] --> Iterator["Iterator (Cursor)"]
Iterator -->|MoveNext / Current| Consumer["Consumidor"]
end
Comparativo de Intenções
| Padrão | Problema Resolvido | Estrutura Central | Exemplo no Ecossistema Moderno |
|---|---|---|---|
| Command | Encapsula uma solicitação como um objeto independente | Interface com métodos Execute() e opcionalmente Undo() |
Filas de tarefas em segundo plano, botões de interface, arquiteturas CQRS |
| Iterator | Percorre elementos de um agregado sem violar o encapsulamento | Abstração de cursor com controle de avanço e leitura | IEnumerable no C#, iteradores em Python, Symbol.iterator no JS |
| Interpreter | Avalia sentenças em uma linguagem ou gramática formal | Árvore sintática abstrata (AST) com nós terminais e não terminais | Parsers de fórmulas matemáticas, validadores de filtros e regras de negócio |
1. Command: Ações como Entidades de Primeira Classe
Ao transformar uma solicitação em um objeto isolado, o padrão Command desacopla o emissor da ação daquele que sabe como executá-la. Esse encapsulamento habilita quatro capacidades arquiteturais imediatas:
- Parâmetros e Agendamento: Comandos podem ser serializados, armazenados em tabelas de auditoria e agendados para execução futura.
- Histórico e Desfazer (Undo / Redo): Guardando uma pilha de comandos executados, a aplicação pode reverter operações aplicando o inverso de cada comando.
- Resiliência e Retries: Em caso de falha de rede temporária, o comando pode ser reinserido em uma fila de mensageria com todas as suas variáveis preservadas.
- Base para CQRS: A segregação de comandos e consultas deriva diretamente dessa separação, onde Commands representam intenções explícitas de mutação de estado.
public interface ICommand
{
Task ExecuteAsync();
Task UndoAsync();
}
public class TransferFundsCommand : ICommand
{
private readonly Account _origin;
private readonly Account _destination;
private readonly decimal _amount;
public TransferFundsCommand(Account origin, Account destination, decimal amount)
{
_origin = origin;
_destination = destination;
_amount = amount;
}
public async Task ExecuteAsync()
{
_origin.Withdraw(_amount);
_destination.Deposit(_amount);
}
public async Task UndoAsync()
{
_destination.Withdraw(_amount);
_origin.Deposit(_amount);
}
}
2. Iterator: Travessia Agnóstica e Avaliação Sob Demanda
Antes da popularização do padrão Iterator, iterar sobre estruturas de dados exigia conhecer os detalhes íntimos da coleção: um array exigia índices inteiros, enquanto uma lista duplamente ligada exigia ponteiros para nós adjacentes.
O Iterator resolve isso expondo uma interface uniforme que fornece acesso sequencial aos elementos de um objeto agregado sem expor sua representação interna.
Nas linguagens modernas, o padrão é tão essencial que foi incorporado diretamente na sintaxe dos compiladores. No C#, construções com a palavra-chave yield return geram automaticamente uma máquina de estados que implementa IEnumerator<T>:
public class PagedDataStream
{
public static async IAsyncEnumerable<string> StreamAuditLogsAsync(ApiClient client)
{
int page = 1;
bool hasMore = true;
while (hasMore)
{
var logs = await client.FetchPageAsync(page++);
if (logs.Count == 0) yield break;
foreach (var log in logs)
{
yield return log; // Avaliação sob demanda (streaming de memória)
}
}
}
}
O consumidor do método consome os dados usando um simples await foreach, sem alocar a lista inteira na memória de uma só vez e sem precisar controlar índices de paginação manualmente.
3. Interpreter: Construindo Interpretadores de Regras
O padrão Interpreter define uma representação gramatical para uma linguagem e um interpretador que utiliza essa representação para avaliar sentenças. Ele é indicado quando um domínio de negócio possui problemas repetitivos que podem ser expressos como sentenças em uma mini-linguagem.
classDiagram
class IExpression {
<<interface>>
+Interpret(Context context) int
}
class NumberExpression {
-int _number
+Interpret(Context context) int
}
class AddExpression {
-IExpression _left
-IExpression _right
+Interpret(Context context) int
}
IExpression <|.. NumberExpression
IExpression <|.. AddExpression
AddExpression --> IExpression : compõe filhos
Considere a avaliação de regras booleanas dinâmicas fornecidas por usuários (por exemplo: Idade > 18 AND Renda > 5000):
public interface IBooleanExpression
{
bool Evaluate(UserContext context);
}
// Nó Terminal (Verificação pontual)
public class AgeGreaterThanExpression : IBooleanExpression
{
private readonly int _minAge;
public AgeGreaterThanExpression(int minAge) => _minAge = minAge;
public bool Evaluate(UserContext context) => context.Age > _minAge;
}
// Nó Não-Terminal (Operador Lógico Composto)
public class AndExpression : IBooleanExpression
{
private readonly IBooleanExpression _left;
private readonly IBooleanExpression _right;
public AndExpression(IBooleanExpression left, IBooleanExpression right)
{
_left = left;
_right = right;
}
public bool Evaluate(UserContext context) =>
_left.Evaluate(context) && _right.Evaluate(context);
}
Ao montar uma árvore de nós sintáticos combinando expressões terminais e operadores lógicos, o motor avalia regras complexas sem depender de bibliotecas externas pesadas e com segurança total contra execução de código arbitrário.