Chain of Responsibility
Encadeia handlers em uma corrente sequencial, dando a cada um a chance de processar uma requisição ou passá-la ao próximo — desacoplando quem envia a requisição de quem a processa.
Intenção
Evitar o acoplamento entre remetente e receptor de uma requisição, dando a mais de um objeto a oportunidade de tratá-la. Os handlers são encadeados; cada um pode processar a requisição e encerrar a corrente, ou passá-la ao próximo handler sem que o remetente saiba quantos handlers existem ou qual deles agirá.
Catalogado pelo GoF (1994) como padrão comportamental, o Chain of
Responsibility (CoR) aparece em pipelines de middleware HTTP (autenticação
→ autorização → throttle → handler de rota), em fluxos de aprovação
hierárquica (funcionário → gerente → diretor → CEO), em sistemas de log
(debug → info → warning → error) e em filtros de eventos de UI. A corrente
é montada em runtime com setNext(), o que permite alterar,
reordenar ou estender os handlers sem modificar o remetente.
Problema
Imagine um sistema de aprovação de despesas corporativas. Diferentes valores exigem aprovação de diferentes níveis hierárquicos. A abordagem ingênua centraliza toda a lógica num único ponto:
// Abordagem ingênua — NÃO faça isso:
function aprovarDespesa(valor: number, aprovador: string): boolean {
if (aprovador === 'chefe' && valor <= 1000) {
console.log('Chefe aprovou');
return true;
} else if (aprovador === 'gerente' && valor <= 5000) {
console.log('Gerente aprovou');
return true;
} else if (aprovador === 'diretor' && valor <= 20000) {
console.log('Diretor aprovou');
return true;
}
console.log('Rejeitado');
return false;
}
// Problema: remetente precisa saber QUEM aprovar; adicionar um nível
// exige modificar esta função; condicionais crescem indefinidamente.
Problemas imediatos: o remetente precisa conhecer todos os aprovadores possíveis e suas regras; adicionar um novo nível de aprovação exige modificar a função central; a lógica de cada nível está misturada num único bloco; não há como reordenar ou substituir um aprovador sem alterar o código que os usa.
O Chain of Responsibility resolve isso transformando cada aprovador em
um handler com a mesma interface, encadeados via setNext().
O remetente envia para o primeiro handler e não sabe — nem precisa saber
— quem processará a requisição.
Solução
O Chain of Responsibility organiza o código em dois elementos centrais:
-
Handler (interface/classe abstrata): declara o método
de tratamento (ex.:
handle(requisicao)) e o método de encadeamento (setNext(handler)). A classe base abstrata implementasetNext()e o repasse padrão: se não processar, delega ao próximo. O handler concreto só precisa implementar sua própria lógica de decisão. -
ConcreteHandler: implementa a lógica de verificação —
decide se processa a requisição (e encerra a corrente) ou chama
super.handle()para passar ao próximo. Ex.:ChefeHandlerque aprova apenas se valor ≤ 1000.
A corrente é montada pelo cliente encadeando handlers com
setNext(). Geralmente retorna this para permitir
encadeamento fluente:
chefe.setNext(gerente).setNext(diretor).
Estrutura
«interface» Handler
┌────────────────────────────────────────┐
│ + setNext(h: Handler): Handler │
│ + handle(req: Requisicao): string|null │
└────────────────────────────────────────┘
▲
HandlerBase (classe abstrata)
┌────────────────────────────────────────┐
│ - proximo: Handler | null │
│ + setNext(h: Handler): Handler │
│ → this.proximo = h; return h │
│ + handle(req): string|null │
│ → this.proximo?.handle(req) ?? null │
└────────────────────────────────────────┘
▲
┌───────────┴────────────┬──────────────────┐
│ │ │
ChefeHandler GerenteHandler DiretorHandler
│ handle(req): │ handle(req): │ handle(req):
│ se valor <= 1000 │ se valor <= 5000 │ se valor <= 20000
│ → processa │ → processa │ → processa
│ senão │ senão │ senão
│ → super.handle(req) │ → super │ → super
Montagem da corrente (cliente):
chefe.setNext(gerente).setNext(diretor)
Fluxo para valor = 3500:
chefe.handle(3500)
→ 3500 > 1000 → passa para gerente
gerente.handle(3500)
→ 3500 <= 5000 → GERENTE APROVA ✓ (corrente encerrada)
Fluxo para valor = 50000:
chefe → gerente → diretor → null (sem handler) → rejeitado
Exemplos de código
Exemplo 1 — Pipeline de aprovação de despesas
Implementação completa com interface Handler, classe base
abstrata que encapsula o repasse e handlers concretos para cada nível
de aprovação. Note o encadeamento fluente via setNext()
retornando o próximo handler.
// ── Interface Handler ─────────────────────────────────────────
interface Handler {
setNext(handler: Handler): Handler;
handle(valor: number): string | null;
}
// ── Classe base abstrata — implementa setNext e repasse padrão ─
abstract class HandlerBase implements Handler {
private proximo: Handler | null = null;
setNext(handler: Handler): Handler {
this.proximo = handler;
return handler; // retorna o próximo → encadeamento fluente
}
handle(valor: number): string | null {
// Comportamento padrão: passa ao próximo ou retorna null
return this.proximo ? this.proximo.handle(valor) : null;
}
}
// ── Handlers concretos ────────────────────────────────────────
class ChefeHandler extends HandlerBase {
handle(valor: number): string | null {
if (valor <= 1_000) {
return `Chefe aprovou R$${valor}`;
}
return super.handle(valor); // passa ao próximo
}
}
class GerenteHandler extends HandlerBase {
handle(valor: number): string | null {
if (valor <= 5_000) {
return `Gerente aprovou R$${valor}`;
}
return super.handle(valor);
}
}
class DiretorHandler extends HandlerBase {
handle(valor: number): string | null {
if (valor <= 20_000) {
return `Diretor aprovou R$${valor}`;
}
return super.handle(valor);
}
}
// Handler final: captura o que escapou da corrente
class RejeitadoHandler extends HandlerBase {
handle(valor: number): string | null {
return `REJEITADO — R$${valor} acima do limite de R$20.000`;
}
}
// ── Montagem da corrente ──────────────────────────────────────
const chefe = new ChefeHandler();
const gerente = new GerenteHandler();
const diretor = new DiretorHandler();
const fim = new RejeitadoHandler();
// Encadeamento fluente: chefe → gerente → diretor → fim
chefe.setNext(gerente).setNext(diretor).setNext(fim);
// ── Uso ──────────────────────────────────────────────────────
const despesas = [500, 3_500, 15_000, 50_000];
for (const valor of despesas) {
const resultado = chefe.handle(valor);
console.log(resultado);
}
// Chefe aprovou R$500
// Gerente aprovou R$3500
// Diretor aprovou R$15000
// REJEITADO — R$50000 acima do limite de R$20.000
<?php
// ── Interface Handler ─────────────────────────────────────────
interface Handler
{
public function setNext(Handler $handler): Handler;
public function handle(int $valor): ?string;
}
// ── Classe base abstrata — implementa setNext e repasse padrão ─
abstract class HandlerBase implements Handler
{
private ?Handler $proximo = null;
public function setNext(Handler $handler): Handler
{
$this->proximo = $handler;
return $handler; // retorna o próximo → encadeamento fluente
}
public function handle(int $valor): ?string
{
return $this->proximo?->handle($valor);
}
}
// ── Handlers concretos ────────────────────────────────────────
class ChefeHandler extends HandlerBase
{
public function handle(int $valor): ?string
{
if ($valor <= 1_000) {
return "Chefe aprovou R\${$valor}";
}
return parent::handle($valor);
}
}
class GerenteHandler extends HandlerBase
{
public function handle(int $valor): ?string
{
if ($valor <= 5_000) {
return "Gerente aprovou R\${$valor}";
}
return parent::handle($valor);
}
}
class DiretorHandler extends HandlerBase
{
public function handle(int $valor): ?string
{
if ($valor <= 20_000) {
return "Diretor aprovou R\${$valor}";
}
return parent::handle($valor);
}
}
// Handler final: captura o que escapou da corrente
class RejeitadoHandler extends HandlerBase
{
public function handle(int $valor): ?string
{
return "REJEITADO — R\${$valor} acima do limite de R\$20.000";
}
}
// ── Montagem da corrente ──────────────────────────────────────
$chefe = new ChefeHandler();
$gerente = new GerenteHandler();
$diretor = new DiretorHandler();
$fim = new RejeitadoHandler();
// Encadeamento fluente: chefe → gerente → diretor → fim
$chefe->setNext($gerente)->setNext($diretor)->setNext($fim);
// ── Uso ──────────────────────────────────────────────────────
$despesas = [500, 3_500, 15_000, 50_000];
foreach ($despesas as $valor) {
echo $chefe->handle($valor) . "\n";
}
// Chefe aprovou R$500
// Gerente aprovou R$3500
// Diretor aprovou R$15000
// REJEITADO — R$50000 acima do limite de R$20.000
Exemplo 2 — Middleware HTTP: autenticação → autorização → throttle
O padrão é idêntico ao de frameworks HTTP como Express.js ou Laravel. Cada middleware decide processar (e passar ao próximo) ou interromper a corrente retornando uma resposta de erro. A corrente é montada na configuração da aplicação, independente das rotas.
// ── Tipos de requisição/resposta HTTP simplificados ───────────
interface Requisicao {
path: string;
token?: string;
perfil?: 'admin' | 'usuario';
ip: string;
}
interface Resposta {
status: number;
corpo: string;
}
// ── Interface do middleware ───────────────────────────────────
interface Middleware {
setNext(m: Middleware): Middleware;
processar(req: Requisicao): Resposta;
}
// ── Classe base abstrata ──────────────────────────────────────
abstract class MiddlewareBase implements Middleware {
private proximo: Middleware | null = null;
setNext(m: Middleware): Middleware {
this.proximo = m;
return m;
}
protected passarAdiante(req: Requisicao): Resposta {
return this.proximo
? this.proximo.processar(req)
: { status: 204, corpo: 'OK (sem handler final)' };
}
abstract processar(req: Requisicao): Resposta;
}
// ── Middleware 1: autenticação ────────────────────────────────
class AutenticacaoMiddleware extends MiddlewareBase {
processar(req: Requisicao): Resposta {
if (!req.token) {
return { status: 401, corpo: 'Não autenticado — token ausente' };
}
console.log('[Auth] token válido');
return this.passarAdiante(req);
}
}
// ── Middleware 2: autorização ─────────────────────────────────
class AutorizacaoMiddleware extends MiddlewareBase {
constructor(private readonly perfilExigido: 'admin' | 'usuario') {
super();
}
processar(req: Requisicao): Resposta {
if (req.perfil !== this.perfilExigido && req.perfil !== 'admin') {
return { status: 403, corpo: `Acesso negado — exige perfil "${this.perfilExigido}"` };
}
console.log(`[Authz] perfil "${req.perfil}" autorizado`);
return this.passarAdiante(req);
}
}
// ── Middleware 3: throttle por IP ─────────────────────────────
class ThrottleMiddleware extends MiddlewareBase {
private readonly contadores = new Map<string, number>();
private readonly limite: number;
constructor(requisicoesPorMinuto: number) {
super();
this.limite = requisicoesPorMinuto;
}
processar(req: Requisicao): Resposta {
const count = (this.contadores.get(req.ip) ?? 0) + 1;
this.contadores.set(req.ip, count);
if (count > this.limite) {
return { status: 429, corpo: `Limite excedido para IP ${req.ip}` };
}
console.log(`[Throttle] ${req.ip}: ${count}/${this.limite}`);
return this.passarAdiante(req);
}
}
// ── Handler final: rota ───────────────────────────────────────
class RotaHandler extends MiddlewareBase {
processar(req: Requisicao): Resposta {
return { status: 200, corpo: `GET ${req.path} — OK` };
}
}
// ── Montagem da corrente ──────────────────────────────────────
const auth = new AutenticacaoMiddleware();
const authz = new AutorizacaoMiddleware('admin');
const throttle = new ThrottleMiddleware(3);
const rota = new RotaHandler();
auth.setNext(authz).setNext(throttle).setNext(rota);
// ── Uso ──────────────────────────────────────────────────────
const reqs: Requisicao[] = [
{ path: '/admin', ip: '10.0.0.1' }, // sem token
{ path: '/admin', token: 'abc', perfil: 'usuario', ip: '10.0.0.2' }, // sem perfil
{ path: '/admin', token: 'xyz', perfil: 'admin', ip: '10.0.0.3' }, // ok
{ path: '/admin', token: 'xyz', perfil: 'admin', ip: '10.0.0.3' }, // ok
{ path: '/admin', token: 'xyz', perfil: 'admin', ip: '10.0.0.3' }, // ok
{ path: '/admin', token: 'xyz', perfil: 'admin', ip: '10.0.0.3' }, // throttle
];
for (const req of reqs) {
const res = auth.processar(req);
console.log(`${res.status} — ${res.corpo}\n`);
}
// 401 — Não autenticado — token ausente
// 403 — Acesso negado — exige perfil "admin"
// [Auth] → [Authz] → [Throttle] → 200 — GET /admin — OK
// ... (repetido)
// 429 — Limite excedido para IP 10.0.0.3
<?php
// ── Tipos simplificados ───────────────────────────────────────
class Requisicao
{
public function __construct(
public readonly string $path,
public readonly string $ip,
public readonly ?string $token = null,
public readonly ?string $perfil = null // 'admin' | 'usuario'
) {}
}
class Resposta
{
public function __construct(
public readonly int $status,
public readonly string $corpo
) {}
}
// ── Interface do middleware ───────────────────────────────────
interface Middleware
{
public function setNext(Middleware $m): Middleware;
public function processar(Requisicao $req): Resposta;
}
// ── Classe base abstrata ──────────────────────────────────────
abstract class MiddlewareBase implements Middleware
{
private ?Middleware $proximo = null;
public function setNext(Middleware $m): Middleware
{
$this->proximo = $m;
return $m;
}
protected function passarAdiante(Requisicao $req): Resposta
{
return $this->proximo
? $this->proximo->processar($req)
: new Resposta(204, 'OK (sem handler final)');
}
abstract public function processar(Requisicao $req): Resposta;
}
// ── Middleware 1: autenticação ────────────────────────────────
class AutenticacaoMiddleware extends MiddlewareBase
{
public function processar(Requisicao $req): Resposta
{
if ($req->token === null) {
return new Resposta(401, 'Não autenticado — token ausente');
}
echo "[Auth] token válido\n";
return $this->passarAdiante($req);
}
}
// ── Middleware 2: autorização ─────────────────────────────────
class AutorizacaoMiddleware extends MiddlewareBase
{
public function __construct(private readonly string $perfilExigido) {}
public function processar(Requisicao $req): Resposta
{
if ($req->perfil !== $this->perfilExigido && $req->perfil !== 'admin') {
return new Resposta(
403,
"Acesso negado — exige perfil \"{$this->perfilExigido}\""
);
}
echo "[Authz] perfil \"{$req->perfil}\" autorizado\n";
return $this->passarAdiante($req);
}
}
// ── Middleware 3: throttle por IP ─────────────────────────────
class ThrottleMiddleware extends MiddlewareBase
{
/** @var array<string, int> */
private array $contadores = [];
public function __construct(private readonly int $limite) {}
public function processar(Requisicao $req): Resposta
{
$this->contadores[$req->ip] = ($this->contadores[$req->ip] ?? 0) + 1;
$count = $this->contadores[$req->ip];
if ($count > $this->limite) {
return new Resposta(429, "Limite excedido para IP {$req->ip}");
}
echo "[Throttle] {$req->ip}: {$count}/{$this->limite}\n";
return $this->passarAdiante($req);
}
}
// ── Handler final: rota ───────────────────────────────────────
class RotaHandler extends MiddlewareBase
{
public function processar(Requisicao $req): Resposta
{
return new Resposta(200, "GET {$req->path} — OK");
}
}
// ── Montagem da corrente ──────────────────────────────────────
$auth = new AutenticacaoMiddleware();
$authz = new AutorizacaoMiddleware('admin');
$throttle = new ThrottleMiddleware(3);
$rota = new RotaHandler();
$auth->setNext($authz)->setNext($throttle)->setNext($rota);
// ── Uso ──────────────────────────────────────────────────────
$reqs = [
new Requisicao('/admin', '10.0.0.1'),
new Requisicao('/admin', '10.0.0.2', 'abc', 'usuario'),
new Requisicao('/admin', '10.0.0.3', 'xyz', 'admin'),
new Requisicao('/admin', '10.0.0.3', 'xyz', 'admin'),
new Requisicao('/admin', '10.0.0.3', 'xyz', 'admin'),
new Requisicao('/admin', '10.0.0.3', 'xyz', 'admin'), // throttle
];
foreach ($reqs as $req) {
$res = $auth->processar($req);
echo "{$res->status} — {$res->corpo}\n\n";
}
Quando usar
- Quando mais de um objeto pode processar uma requisição e o processador não é conhecido a priori: o remetente não deve embutir lógica de roteamento. A corrente decide dinamicamente quem age, baseado no estado da requisição.
- Quando você quer enviar uma requisição para um de vários objetos sem especificar o receptor explicitamente: pipelines de middleware, filtros de eventos, fluxos de validação em estágios.
- Quando o conjunto de handlers deve ser configurável em runtime: a corrente é montada pelo cliente; handlers podem ser adicionados, removidos ou reordenados sem modificar o remetente nem os handlers existentes.
- Para implementar pipelines de validação ou pré-processamento onde cada etapa é autônoma: cada handler conhece apenas a sua regra e o contrato de repasse — não conhece os outros.
Quando evitar
- Quando a garantia de processamento é obrigatória: o CoR não garante que a requisição será processada — ela pode cair no vazio se nenhum handler a aceitar. Se o processamento é obrigatório, use um handler final de fallback ou escolha outro padrão.
- Quando a corrente é muito longa ou aninhada: correntes longas são difíceis de depurar — rastrear por qual handler a requisição passou exige logging explícito em cada elo. O stack trace pode ser profundo e a origem do problema, obscura.
- Quando há exatamente um handler possível e ele é conhecido estaticamente: nesse caso, uma chamada direta ou Strategy é mais simples e mais legível.
Prós e contras
Prós
- Desacopla o remetente dos receptores — o remetente conhece apenas o primeiro handler da corrente.
- Respeita o Princípio da Responsabilidade Única: cada handler tem uma regra de tratamento.
- Respeita o Princípio Aberto/Fechado: novos handlers podem ser inseridos sem modificar os existentes ou o remetente.
- A corrente é configurável em runtime — handlers podem ser reordenados, adicionados ou removidos dinamicamente.
Contras
- Não garante processamento — a requisição pode cair no vazio se nenhum handler agir (requer handler de fallback intencional).
- Dificulta a depuração: o fluxo de execução não é óbvio apenas lendo o código que monta a corrente.
- Correntes longas podem ter impacto de performance — cada handler acrescenta uma chamada de método.
- A responsabilidade pode se tornar difusa: quando muitos handlers têm regras sobrepostas, é difícil saber qual processará.
Armadilhas comuns
1. Requisição que cai no vazio (sem handler final)
O erro mais comum: montar a corrente sem um handler de fallback no final.
Se nenhum handler da corrente aceitar a requisição, ela retorna
null silenciosamente — e o código que chamou pode não
estar preparado para isso, causando NullPointerException
ou comportamento indefinido.
Regra prática: sempre adicione um handler final explícito
que trate o caso "nenhum handler anterior quis processar". Pode ser um
logger de warning, um lançador de exceção ou uma resposta de erro padrão.
Nunca confie em null implícito como indicador de "não tratado".
2. Corrente que não encerra (loop)
Se um handler chamar super.handle() sem verificar a condição
— ou se um handler for acidentalmente adicionado ao final de sua própria
corrente — o CoR pode entrar em loop infinito ou em recursão profunda.
Garanta que cada ConcreteHandler tem uma condição clara para encerrar a
corrente e que nenhum handler referencia a si mesmo como próximo.
3. Responsabilidade difusa — handlers que "ouvem" em vez de "filtrar"
Um antipadrão frequente: handlers que sempre chamam super.handle()
independentemente do resultado — processam E repassam. Isso transforma o
CoR num Observer disfarçado, onde todos os handlers executam sempre. O
Princípio da Responsabilidade Única do CoR é: ou você trata e encerra, ou
não trata e passa. Se o domínio exige que todos tratem, use Observer ou
uma lista de listeners.
4. Chain of Responsibility vs Strategy — distinção de intenção
A confusão surge porque ambos encapsulam comportamento em objetos com interface comum. A diferença é estrutural e de intenção:
- Strategy: exatamente um algoritmo é escolhido pelo contexto e aplicado. Não há corrente, não há repasse. O contexto decide qual Strategy usar — e só aquela executa.
- Chain of Responsibility: zero ou mais handlers processam a requisição sequencialmente, cada um podendo encerrar ou continuar. Nenhum conhece os outros. O conjunto que processará é determinado em runtime pelo estado da requisição.
5. Distinção com Decorator
Estruturalmente similares: ambos encadeiam objetos com a mesma interface. A diferença é que o Decorator sempre delega ao próximo (acrescentando comportamento antes ou depois, nunca interrompendo) e tem como intenção adicionar responsabilidades ao objeto decorado. O CoR pode interromper a corrente sem delegar — quando processa a requisição, os handlers seguintes não são chamados. Se o objeto sempre passa adiante E adiciona comportamento, é Decorator. Se pode encerrar a corrente sem passar, é CoR.
Padrões relacionados
O Chain of Responsibility é frequentemente combinado ou comparado com padrões que também lidam com despacho de ações e composição de comportamento:
O Command e o CoR frequentemente aparecem juntos: o Command encapsula a requisição como objeto, e o CoR decide qual handler a processará — o Command carrega os dados da ação; o CoR roteia quem age sobre eles. O Decorator é o padrão de maior semelhança estrutural: ambos encadeiam objetos com a mesma interface. A diferença central é que o Decorator sempre delega ao próximo e adiciona comportamento sem interromper a cadeia, enquanto o CoR pode encerrar a corrente ao processar a requisição — os dois têm intenções opostas quanto ao fluxo de controle. O Visitor pode ser usado para processar os nós de uma corrente CoR sem modificar as classes de handler, separando a lógica de traversal da lógica de tratamento.