Chain of Responsibility
Intenção (GoF)
Seção intitulada “Intenção (GoF)”Evitar o acoplamento do remetente de uma requisição ao seu receptor, dando a mais de um objeto a chance de tratá-la. Encadear os objetos receptores e passar a requisição ao longo da cadeia até que um deles a trate.
Problema
Seção intitulada “Problema”Um objeto emite uma requisição, mas qual objeto deve tratá-la não é fixo nem conhecido a priori. Codificar essa decisão como uma sequência de if/else no emissor cria acoplamento rígido, viola o OCP a cada novo caso e concentra responsabilidades. Exemplos típicos: pipelines de validação, filtros HTTP, escalonamento de aprovações, tratamento de eventos em cadeia de componentes.
Estrutura
Seção intitulada “Estrutura”- Handler: contrato que declara
handle(request). - BaseHandler (opcional): classe abstrata que guarda a referência ao próximo handler (
next) e implementa o repasse padrão — interfaces Java não mantêm estado. - ConcreteHandler: decide se trata a requisição, se a repassa, ou ambos.
- Client: monta a cadeia e submete a requisição ao primeiro elo.
Há duas variantes: a pura (exatamente um handler trata e a cadeia para) e a de pipeline/filtro (todos os elos aplicáveis processam em sequência, estilo middleware).
Consequências e trade-offs
Seção intitulada “Consequências e trade-offs”Prós
- Reduz acoplamento entre emissor e receptor.
- Flexibilidade: adicionar, remover ou reordenar handlers em runtime.
- Cada handler tem responsabilidade única, favorecendo o SRP - Single Responsibility Principle.
Contras
- Sem garantia de tratamento: uma requisição pode chegar ao fim sem ser atendida.
- Difícil de depurar: o fluxo é dinâmico e espalhado.
- Cadeias longas podem ter custo de percurso e ordem sutilmente relevante.
Quando NÃO usar
Seção intitulada “Quando NÃO usar”Quando há um único receptor conhecido, quando a ordem é irrelevante e fixa, ou quando um simples switch/mapa de handlers já resolve com menos indireção.
Exemplo em Java
Seção intitulada “Exemplo em Java”Antes: condicionais aninhadas
Seção intitulada “Antes: condicionais aninhadas”public class RequestValidator { public void validate(Request req) { if (req.getToken() == null || req.getToken().isBlank()) { throw new ValidationException("token ausente"); } if (!hasRole(req, "USER")) { throw new ValidationException("sem permissao"); } if (req.getBody() == null) { throw new ValidationException("body vazio"); } if (rateLimitExceeded(req.getClientId())) { throw new ValidationException("rate limit excedido"); } }}Cada nova regra edita esta classe; a ordem está soldada e nada é reutilizável.
Depois: cadeia de handlers
Seção intitulada “Depois: cadeia de handlers”public record Request(String token, String clientId, String body, Set<String> roles) {}
public abstract class ValidationHandler {
private ValidationHandler next;
public ValidationHandler linkTo(ValidationHandler next) { this.next = next; return next; }
public final void handle(Request request) { check(request); if (next != null) { next.handle(request); } }
protected abstract void check(Request request);}
public class AuthenticationHandler extends ValidationHandler { protected void check(Request request) { if (request.token() == null || request.token().isBlank()) { throw new ValidationException("token ausente"); } }}
public class AuthorizationHandler extends ValidationHandler { protected void check(Request request) { if (!request.roles().contains("USER")) { throw new ValidationException("sem permissao"); } }}
public class BodyHandler extends ValidationHandler { protected void check(Request request) { if (request.body() == null) { throw new ValidationException("body vazio"); } }}
public class RateLimitHandler extends ValidationHandler {
private final RateLimiter limiter;
public RateLimitHandler(RateLimiter limiter) { this.limiter = limiter; }
protected void check(Request request) { if (limiter.exceeded(request.clientId())) { throw new ValidationException("rate limit excedido"); } }}Montagem e uso pelo cliente:
public class ValidationPipeline {
private final ValidationHandler entry;
public ValidationPipeline(RateLimiter limiter) { this.entry = new AuthenticationHandler(); entry.linkTo(new AuthorizationHandler()) .linkTo(new BodyHandler()) .linkTo(new RateLimitHandler(limiter)); }
public void run(Request request) { entry.handle(request); }}Adicionar uma regra é criar um handler e inseri-lo na montagem — o restante permanece intocado.
Diagrama
Seção intitulada “Diagrama”classDiagram
class ValidationHandler {
-ValidationHandler next
+linkTo(next) ValidationHandler
+handle(request) void
#check(request)* void
}
class AuthenticationHandler {
#check(request) void
}
class AuthorizationHandler {
#check(request) void
}
class BodyHandler {
#check(request) void
}
class RateLimitHandler {
#check(request) void
}
ValidationHandler <|-- AuthenticationHandler
ValidationHandler <|-- AuthorizationHandler
ValidationHandler <|-- BodyHandler
ValidationHandler <|-- RateLimitHandler
ValidationHandler o--> ValidationHandler : next
Middleware: o mesmo padrão em outra roupa
Seção intitulada “Middleware: o mesmo padrão em outra roupa”A pilha de middleware de frameworks web (Servlet Filter, interceptors, middlewares de Express/ASP.NET) é uma Chain of Responsibility na variante pipeline. Cada middleware recebe a requisição, pode processá-la, mutar contexto e decidir chamar (ou não) o próximo elo — a assinatura (request, next) é literalmente o next da cadeia. Autenticação, logging, CORS e compressão são handlers plugáveis. É também assim que o padrão se encaixa em Ports e Adapters na Arquitetura Hexagonal: filtros de borda antes de chegar ao caso de uso.
Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”- Cadeia que “engole” requisições: nenhum handler trata e nenhum erro é sinalizado. Sempre defina um comportamento de fim de cadeia (handler default ou exceção).
- Handler que faz coisas demais, quebrando o SRP - Single Responsibility Principle e anulando a reutilização.
- Ordem implícita e frágil: se o resultado depende criticamente da sequência, documente e teste essa ordem.
- Estado compartilhado mutável mal controlado trafegando pela cadeia, gerando efeitos colaterais difíceis de rastrear.
Relações
Seção intitulada “Relações”- Padrão comportamental do Padrões Comportamentais / Design Patterns (GoF).
- Frequentemente combinado com Command (cada handler pode encapsular ou disparar comandos).
- Parente de Proxy e Facade quando usado como camada de borda.
- Suporta SRP - Single Responsibility Principle e OCP - Open-Closed Principle.
- Índice: Design Patterns (GoF) · Catálogo de Patterns.
Perguntas de revisão
Seção intitulada “Perguntas de revisão”1. Qual acoplamento a Chain of Responsibility elimina?
Resposta
O acoplamento entre o emissor de uma requisição e o receptor concreto que a trata. O emissor apenas entrega a requisição ao início da cadeia, sem saber qual handler (ou quantos) vai processá-la.
2. Qual o principal risco da variante “pura” do padrão?
Resposta
A requisição pode percorrer toda a cadeia sem ser tratada por ninguém, terminando silenciosamente. Mitiga-se com um handler default ao final ou lançando exceção quando nenhum elo assume a responsabilidade.
3. Como o padrão se relaciona com middleware/filtros web?
Resposta
Middleware é a variante pipeline da CoR: cada elo recebe (request, next), processa e decide invocar o próximo. Filtros de autenticação, logging e CORS são handlers plugáveis e reordenáveis, exatamente a estrutura da cadeia.
4. Em que situação a Chain of Responsibility é overengineering?
Resposta
Quando existe um único receptor conhecido e fixo, ou quando a seleção do handler é uma decisão simples resolvível com um switch ou um mapa tipo -> handler. A cadeia só compensa quando há múltiplos handlers, composição/ordem dinâmica ou extensão frequente.