Padrões comportamentais: Command e Chain of Responsibility
1. Introdução aos Padrões Comportamentais na Arquitetura de Software
1.1. O papel dos padrões comportamentais no design de interações entre objetos
Padrões comportamentais tratam da comunicação e da atribuição de responsabilidades entre objetos. Enquanto padrões criacionais lidam com a instanciação e padrões estruturais com a composição, os comportamentais definem como os objetos colaboram para realizar tarefas complexas. Eles promovem baixo acoplamento e alta coesão, permitindo que sistemas evoluam sem quebrar fluxos de interação estabelecidos.
1.2. Visão geral do Command e Chain of Responsibility
O Command encapsula uma requisição como um objeto, permitindo parametrizar clientes com diferentes requisições, enfileirar ou registrar operações e suportar desfazer/refazer. O Chain of Responsibility constrói uma cadeia de objetos processadores, onde cada um decide se processa a requisição ou a repassa ao próximo. Ambos promovem desacoplamento, mas em direções distintas: Command separa quem invoca de quem executa; Chain of Responsibility separa quem envia de quem processa ao longo de uma pipeline.
1.3. Contexto de uso
Command é ideal em sistemas com alta variabilidade de ações (editores, sistemas de automação) e necessidade de histórico. Chain of Responsibility brilha em pipelines de tratamento (middlewares, validações, filtros) onde a ordem e a composição dos processadores mudam dinamicamente.
2. Padrão Command: Encapsulando Requisições como Objetos
2.1. Definição e estrutura
O padrão envolve quatro participantes:
- Command: interface que declara o método execute()
- ConcreteCommand: implementa execute() chamando operações no Receiver
- Invoker: solicita a execução do comando
- Receiver: sabe como realizar a operação real
2.2. Vantagens arquiteturais
Command desacopla completamente o emissor da lógica de execução, permitindo:
- Filas de tarefas e execução assíncrona
- Suporte a undo/redo via histórico de comandos
- Composição de comandos (macro comandos)
- Logging e auditoria de ações
2.3. Exemplo conceitual: editor de texto
Em um editor, cada ação do usuário (copiar, colar, desfazer) é um comando. O Invoker (interface do editor) não sabe como copiar ou colar — apenas chama execute(). O Receiver (documento) realiza a operação. Comandos de desfazer armazenam estado anterior para reversão.
3. Implementação e Aplicações do Command
3.1. Exemplo de código
// Interface Command
public interface Command {
void execute();
void undo();
}
// Receiver: Editor de Texto
public class TextEditor {
private String text = "";
public void append(String content) {
text += content;
}
public void deleteLast(int length) {
if (text.length() >= length)
text = text.substring(0, text.length() - length);
}
public String getText() {
return text;
}
}
// ConcreteCommand: Comando de Inserção
public class InsertCommand implements Command {
private TextEditor editor;
private String content;
public InsertCommand(TextEditor editor, String content) {
this.editor = editor;
this.content = content;
}
public void execute() {
editor.append(content);
}
public void undo() {
editor.deleteLast(content.length());
}
}
// Invoker com suporte a histórico
public class CommandInvoker {
private Stack<Command> history = new Stack<>();
public void executeCommand(Command cmd) {
cmd.execute();
history.push(cmd);
}
public void undoLast() {
if (!history.isEmpty()) {
Command cmd = history.pop();
cmd.undo();
}
}
}
3.2. Uso em filas de tarefas e logging
Command permite serializar requisições para filas (RabbitMQ, Kafka) e registrar cada ação para auditoria. Macro comandos (Composite) agrupam múltiplos comandos em uma única operação atômica.
3.3. Integração com Composite
Um MacroCommand implementa Command e mantém uma lista de Command filhos. execute() itera sobre todos; undo() reverte na ordem inversa.
4. Padrão Chain of Responsibility: Cadeia de Responsabilidade
4.1. Definição e estrutura
Participantes:
- Handler: interface com método handle(request) e referência ao próximo handler
- ConcreteHandler: processa a requisição ou a repassa
- Client: monta a cadeia e envia a requisição ao primeiro handler
4.2. Vantagens arquiteturais
- Flexibilidade na composição de pipelines: handlers podem ser adicionados, removidos ou reordenados sem alterar o cliente
- Responsabilidade única: cada handler faz apenas uma coisa
- Suporte a processamento condicional: um handler pode interromper a cadeia
4.3. Exemplo conceitual: validação de formulários
Um formulário passa por validação de campos obrigatórios, verificação de formato de e-mail, autorização do usuário e formatação de saída. Cada etapa é um handler que pode aprovar ou rejeitar a requisição.
5. Implementação e Aplicações do Chain of Responsibility
5.1. Exemplo de código: processamento de requisições HTTP
// Handler base
public abstract class HttpHandler {
protected HttpHandler next;
public void setNext(HttpHandler next) {
this.next = next;
}
public abstract void handle(HttpRequest request);
protected void passToNext(HttpRequest request) {
if (next != null)
next.handle(request);
}
}
// ConcreteHandler: Autenticação
public class AuthHandler extends HttpHandler {
public void handle(HttpRequest request) {
if (request.hasValidToken()) {
System.out.println("Autenticação OK");
passToNext(request);
} else {
System.out.println("Falha na autenticação");
}
}
}
// ConcreteHandler: Logging
public class LoggingHandler extends HttpHandler {
public void handle(HttpRequest request) {
System.out.println("Log: " + request.getUrl());
passToNext(request);
}
}
// ConcreteHandler: Cache
public class CacheHandler extends HttpHandler {
public void handle(HttpRequest request) {
if (cacheContains(request)) {
System.out.println("Retornando do cache");
// Não passa adiante
} else {
passToNext(request);
}
}
}
// Uso
HttpHandler auth = new AuthHandler();
HttpHandler logging = new LoggingHandler();
HttpHandler cache = new CacheHandler();
auth.setNext(logging);
logging.setNext(cache);
auth.handle(request);
5.2. Uso em middlewares e filtros
Frameworks como ASP.NET Core, Express.js e Spring utilizam Chain of Responsibility para pipelines de middleware: autenticação, compressão, CORS, roteamento, etc.
5.3. Diferenças com Decorator
Decorator adiciona comportamento dinamicamente a um objeto, mas todos os decorators processam a requisição. Chain of Responsibility permite que um handler decida interromper o fluxo. Decorator é estrutural; Chain of Responsibility é comportamental.
6. Comparação e Combinação entre Command e Chain of Responsibility
6.1. Quando usar cada um
- Command: quando você precisa encapsular uma ação como objeto, permitir undo/redo, enfileirar ou registrar execuções
- Chain of Responsibility: quando você tem uma sequência de processamento que pode variar em ordem ou composição, e cada etapa pode decidir interromper
6.2. Combinação prática
Uma cadeia de handlers pode executar comandos sequenciais. Cada handler no Chain of Responsibility pode invocar um Command para realizar sua tarefa, combinando a flexibilidade da cadeia com o encapsulamento do comando.
6.3. Trade-offs arquiteturais
- Complexidade: ambos aumentam o número de classes, mas reduzem acoplamento
- Manutenibilidade: cadeias muito longas dificultam debug; comandos excessivos aumentam boilerplate
- Desempenho: Chain of Responsibility tem overhead de chamadas em cadeia; Command tem overhead de objetos
7. Exemplo Integrado: Sistema de Processamento de Pedidos
7.1. Cenário
Um pedido de e-commerce passa por validação de estoque, cálculo de impostos, aplicação de desconto e envio de notificação.
7.2. Chain of Responsibility para pipeline de validação
public class StockHandler extends OrderHandler {
public void handle(Order order) {
if (stockAvailable(order)) {
System.out.println("Estoque verificado");
passToNext(order);
} else {
System.out.println("Sem estoque");
}
}
}
public class TaxHandler extends OrderHandler {
public void handle(Order order) {
order.setTax(calculateTax(order));
System.out.println("Imposto calculado");
passToNext(order);
}
}
7.3. Command para encapsular ações e undo
public class CalculateTaxCommand implements Command {
private Order order;
private double previousTax;
public void execute() {
previousTax = order.getTax();
order.setTax(calculateTax(order));
}
public void undo() {
order.setTax(previousTax);
}
}
public class SendEmailCommand implements Command {
private Order order;
public void execute() {
emailService.sendConfirmation(order);
}
public void undo() {
emailService.cancelConfirmation(order);
}
}
A cadeia de handlers invoca comandos em cada etapa. O histórico de comandos permite desfazer todo o processamento do pedido.
8. Considerações Finais e Boas Práticas
8.1. Relação com padrões vizinhos
- Observer: Chain of Responsibility pode usar Observer para notificar mudanças na cadeia
- Strategy: Command pode usar Strategy para variar algoritmos de execução
- State: ambos podem ser combinados com State para alterar comportamento em runtime
- Visitor: Visitor percorre estruturas; Chain of Responsibility percorre handlers
8.2. Dicas de implementação
- Evite cadeias muito longas (mais de 5-7 handlers); considere quebrar em subcadeias
- Use interfaces claras e genéricas para Command e Handler
- Considere async/await para operações I/O em Command
- Documente a ordem esperada da cadeia
8.3. Próximos passos
Os próximos artigos abordarão Observer e Strategy, aprofundando como padrões comportamentais resolvem problemas de comunicação entre objetos e variação de algoritmos.
Referências
- Command Pattern - Refactoring Guru — Explicação detalhada com diagramas UML e exemplos em várias linguagens
- Chain of Responsibility - Refactoring Guru — Guia completo com exemplos práticos e comparações
- Command Pattern - SourceMaking — Tutorial com casos de uso reais e implementação em Java
- Chain of Responsibility - SourceMaking — Exemplos de middlewares e filtros em sistemas web
- Design Patterns: Elements of Reusable Object-Oriented Software (GoF) — Livro clássico que definiu ambos os padrões, com descrições canônicas
- Command Pattern - Microsoft Docs — Implementação em C# com exemplos de undo/redo e filas
- Chain of Responsibility in ASP.NET Core Middleware — Documentação oficial mostrando o padrão em ação no pipeline de requisições HTTP