Chain of Responsibility
Evita acoplar o remetente de uma requisição ao seu receptor, dando a mais de um objeto a chance de tratar a requisição. Encadeia os objetos receptores e passa a requisição ao longo da cadeia até que um objeto a trate.
Intenção
Desacoplar o remetente de uma requisição do seu receptor, permitindo que múltiplos objetos tratem a requisição em sequência. Cada handler decide processar a requisição ou passá-la adiante para o próximo handler da cadeia.
Problema
Você tem uma requisição que poderia ser tratada por vários objetos diferentes, e não quer que o remetente saiba qual objeto específico vai tratá-la. Fixar a seleção de handler no código cria acoplamento forte e torna o sistema rígido quando novos handlers precisam ser adicionados ou a ordem precisa mudar.
Solução
Defina uma interface Handler com um método para processar requisições e uma referência ao próximo handler. Cada ConcreteHandler processa a requisição se puder, ou a encaminha para seu sucessor. O cliente envia a requisição ao primeiro handler da cadeia sem saber qual handler vai processá-la ao final. Handlers podem ser reordenados, adicionados, ou removidos sem afetar o cliente.
Participantes
- Handler — define a interface para tratar requisições e opcionalmente mantém uma referência ao próximo handler
- BaseHandler — classe abstrata opcional que implementa o comportamento padrão de encadeamento (encaminhar ao próximo handler)
- ConcreteHandler — trata as requisições pelas quais é responsável; pode acessar seu sucessor para encaminhar requisições não tratadas
- Client — inicia a requisição no primeiro handler da cadeia
Vantagens
- Reduz acoplamento — o remetente não precisa saber qual handler processa a requisição
- Composição flexível de cadeia — handlers podem ser adicionados, removidos, ou reordenados em tempo de execução
- Cada handler tem uma única responsabilidade, seguindo o Princípio da Responsabilidade Única
- Uma requisição pode ser tratada por múltiplos handlers em sequência (processamento estilo middleware)
Desvantagens
- Não há garantia de que uma requisição será tratada — ela pode cair no fim da cadeia sem tratamento
- A depuração pode ser difícil porque o fluxo passa por múltiplos objetos
- A configuração da cadeia precisa estar correta; uma ordem incorreta pode produzir resultados errados
- O desempenho pode sofrer com cadeias muito longas, já que cada handler adiciona overhead
Analogia do mundo real
Ligar para a central de atendimento de uma empresa é uma cadeia de responsabilidade. Sua ligação vai primeiro para um atendente de linha de frente. Se ele não conseguir resolver seu problema, escala para um especialista. Se o especialista também não conseguir ajudar, vai para um gerente. Você, o chamador, não escolhe quem trata seu problema — você só entra na cadeia e cada nível decide se trata ou escala.
Casos de uso
- Pipelines de middleware HTTP — autenticação, logging, CORS, rate limiting, compressão
- Cadeias de validação — cada validador checa uma regra e passa adiante se válida
- Roteamento de tickets de suporte — escalar do nível 1 para o nível 2 e depois nível 3
- Propagação de eventos no DOM — eventos se propagam de elementos filhos para elementos pais
- Fluxos de aprovação — gerente, diretor, VP, cada um com diferentes níveis de autoridade
- Tratamento de exceções — blocos catch formam uma cadeia que trata diferentes tipos de exceção
Exemplos de código
import java.util.HashMap;
import java.util.Map;
final class Request {
final String method;
final String path;
final Map<String, String> headers;
final String clientIp;
Request(String method, String path, Map<String, String> headers, String clientIp) {
this.method = method;
this.path = path;
this.headers = headers;
this.clientIp = clientIp;
}
}
interface Handler {
Handler setNext(Handler handler);
Request handle(Request request);
}
abstract class BaseHandler implements Handler {
private Handler next;
@Override
public Handler setNext(Handler handler) {
this.next = handler;
return handler; // enables chaining: a.setNext(b).setNext(c)
}
@Override
public Request handle(Request request) {
if (next != null) {
return next.handle(request);
}
return request; // end of chain -- request passed all checks
}
}
final class AuthHandler extends BaseHandler {
@Override
public Request handle(Request request) {
if (!request.headers.containsKey("Authorization")) {
System.out.println("401: missing Authorization header");
return null;
}
return super.handle(request);
}
}
final class RateLimitHandler extends BaseHandler {
private final Map<String, Integer> hitsByIp = new HashMap<>();
@Override
public Request handle(Request request) {
int hits = hitsByIp.merge(request.clientIp, 1, Integer::sum);
if (hits > 100) {
System.out.println("429: rate limit exceeded for " + request.clientIp);
return null;
}
return super.handle(request);
}
}
final class LoggingHandler extends BaseHandler {
@Override
public Request handle(Request request) {
System.out.println("[log] " + request.method + " " + request.path);
return super.handle(request);
}
}
public class ChainOfResponsibilityDemo {
public static void main(String[] args) {
Handler auth = new AuthHandler();
auth.setNext(new RateLimitHandler()).setNext(new LoggingHandler());
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer token123");
Request request = new Request("GET", "/api/users", headers, "10.0.0.1");
Request result = auth.handle(request);
System.out.println(result != null ? "Request passed the chain" : "Request rejected");
}
}Um pipeline de middleware HTTP onde AuthHandler, RateLimitHandler, e LoggingHandler cada um inspeciona a requisição e decide se passa ao próximo handler ou rejeita.
Padrões relacionados
Command
Encapsula uma requisição como um objeto, permitindo parametrizar clientes com diferentes requisições, enfileirar ou registrar requisições, e suportar operações que podem ser desfeitas.