Pular para o conteúdo

Chain of Responsibility

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.

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.

  • 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).

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 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.

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.

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.

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

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.

  • 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.

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.