O padrão de design Chain of Responsibility PHP é uma técnica fundamental no desenvolvimento de software. Ele permite que um pedido passe por uma cadeia de possíveis processadores até que algum deles o trate efetivamente. Dessa forma, este padrão é especialmente valioso para a construção de sistemas flexíveis e desacoplados, onde a lógica de processamento pode ser alterada dinamicamente e distribuída entre vários objetos. Neste artigo, exploraremos como implementar esse padrão Chain of Responsibility PHP.
Para saber mais sobre a origem e os fundamentos do Chain of Responsibility, você pode consultar o GoF Design Patterns que oferece uma introdução completa.
O que é um Padrão Comportamental?
O Chain of Responsibility PHP, por exemplo, exemplifica um padrão comportamental clássico. Esse padrão faz parte de uma categoria de padrões de design que, por sua vez, concentra a interação e a delegação de responsabilidades entre objetos e classes. Além disso, esses objetos organizam e passam as tarefas ou solicitações ao longo da cadeia, enquanto cada um deles tem a capacidade de processar ou, se necessário, encaminhar a solicitação adiante. Consequentemente, esse padrão facilita a colaboração entre objetos, promovendo assim uma comunicação eficaz e, ao mesmo tempo, evitando a criação de dependências rígidas entre eles.
Leia mais sobre padrões comportamentais no site oficial do PHP.net.
Conceito Básico do Chain of Responsibility
O Chain of Responsibility, por sua vez, possibilita que múltiplos objetos tratem uma solicitação, enquanto forma uma sequência onde cada objeto tem a oportunidade de processar o pedido. Além disso, o padrão também se baseia na ideia de que cada objeto na cadeia pode, a qualquer momento, decidir se processa a solicitação ou se a encaminha ao próximo objeto. Dessa maneira, ele promove grande modularidade e flexibilidade no manejo das operações.
Vantagens do Chain of Responsibility
- Desacoplamento: O emissor da solicitação não precisa saber quem trata o pedido, além disso, não precisa saber como alguém o processa.
- Flexibilidade: O desenvolvedor, por sua vez, pode facilmente adicionar novos tratadores à cadeia sem precisar modificar o código existente.
- Distribuição de responsabilidades: O padrão, consequentemente, facilita que diferentes objetos compartilhem responsabilidades.
Exemplo Prático: Sistema de Aprovação de Compras
Por exemplo, imagine um sistema onde as solicitações de compra passam por várias verificações antes que alguém as aprove. Nesse sistema, cada verificação é representada por um handler.

- Handler: Esta classe abstrata, primeiramente, implementa o método
setNext()
para encadear manipuladores e, além disso, define o método abstratohandle()
para tratar as solicitações. Por isso, as classes que herdam deHandler
implementam esse método. - ConcreteHandler: Classes como
GerenteAprovacaoHandler
,DiretorAprovacaoHandler
eCIOAprovacaoHandler
estendem a classe abstrataHandler
. Dessa forma, cada uma dessas classes implementa o métodohandle()
. Nesse contexto, cada classe pode tratar parte da solicitação ou, alternativamente, passá-la ao próximo manipulador na cadeia.
Como é o fluxo de dados entre os objetos?
- Início da Cadeia: O cliente envia uma solicitação ao primeiro manipulador na cadeia.
- Processamento de Solicitações:
- Primeiro, cada manipulador verifica se pode tratar a solicitação.
- Se um manipulador pode tratar a solicitação, ele o faz e pode decidir se passará a solicitação adiante.
- Caso contrário, ele simplesmente passa a solicitação para o próximo manipulador na cadeia.
- Término do Processamento:
- Se nenhum manipulador na cadeia puder tratar a solicitação, o sistema pode descartar a solicitação ou tratá-la de uma maneira padrão, dependendo da implementação.
- Por outro lado, se a cadeia conseguir tratar a solicitação em qualquer ponto, o processamento termina conforme a lógica definida.
Este fluxo mostra claramente como cada handler na cadeia tem a oportunidade de processar a solicitação. Se não puderem lidar com a solicitação por conta própria devido ao valor, passam a solicitação para o próximo handler na cadeia. No exemplo prático:

- Início do Processo: O cliente envia uma solicitação (
request
) ao objetoGerenteAprovacaoHandler
. - Verificação pelo Gerente:
- O
GerenteAprovacaoHandler
verifica se o valor da solicitação é menor que 1000. - Se sim, o gerente aprova a solicitação e o processo termina.
- Caso contrário, o
GerenteAprovacaoHandler
envia a solicitação aoDiretorAprovacaoHandler
para mais verificações. - Verificação pelo Diretor:
- O
DiretorAprovacaoHandler
avalia a solicitação. - Se o valor da solicitação é menor que 10000, o diretor aprova a solicitação e o processo termina.
- Se o valor é igual ou maior que 10000, o
DiretorAprovacaoHandler
envia a solicitação aoCIOAprovacaoHandler
. - Verificação pelo CIO:
- O
CIOAprovacaoHandler
aprova a solicitação independentemente do valor. Assim, o processo termina com a aprovação do CIO.
Definição da Classe Abstrata Handler
Primeiramente, ao projetar a classe abstrata Handler no padrão Chain of Responsibility, é importante que o desenvolvedor decida como encadear os manipuladores. Existem duas formas principais de atribuir os manipuladores concretos: através de um método ou diretamente pelo construtor. Dependendo da abordagem que o desenvolvedor escolher, ele precisará ajustar a classe abstrata para dar suporte à respectiva forma de encadeamento.
Abordagem 1: Atribuição via Método
Nesta abordagem, o desenvolvedor atribui o próximo manipulador na cadeia durante a execução, utilizando o método setNext()
. Essa estratégia proporciona flexibilidade, pois o desenvolvedor pode configurar ou modificar a cadeia dinamicamente.
A classe Handler
para essa abordagem inclui o método setNext()
, permitindo que o desenvolvedor atribua o próximo manipulador manualmente:
abstract class Handler
{
protected $nextHandler;
// Método para definir o próximo manipulador na cadeia
public function setNext(Handler $handler): Handler
{
$this->nextHandler = $handler;
return $handler;
}
// Método abstrato que as classes concretas devem implementar
abstract public function handle(Request $request): ?string;
}
Neste caso, o desenvolvedor configura o próximo manipulador usando o método setNext(), permitindo que o encadeamento dos manipuladores ocorra conforme necessário em tempo de execução. Essa abordagem é útil quando a cadeia pode mudar ou crescer durante o ciclo de vida da aplicação, oferecendo maior flexibilidade.
Abordagem 2: Atribuição via Construtor
Por outro lado, na atribuição via construtor, o desenvolvedor define o próximo manipulador no momento da criação do objeto. Com essa abordagem, o desenvolvedor configura a cadeia de responsabilidade diretamente na instância dos manipuladores, garantindo que ela permaneça estável e imutável ao longo do tempo.
Para suportar essa abordagem, a classe abstrata Handler
deve receber o próximo manipulador como um parâmetro do construtor, em vez de utilizar o método setNext()
. A seguir, veja como o desenvolvedor pode ajustar a implementação da classe abstrata Handler
para essa abordagem:
abstract class Handler
{
protected $nextHandler;
// Construtor que aceita o próximo manipulador na cadeia
public function __construct(Handler $nextHandler = null)
{
$this->nextHandler = $nextHandler;
}
// Método abstrato que as classes concretas devem implementar
abstract public function handle(Request $request): ?string;
}
Com essa implementação, o próximo manipulador é passado diretamente no momento da criação do objeto. Isso garante uma cadeia de responsabilidade mais rígida, onde o encadeamento não pode ser alterado facilmente após a criação.
Qual abordagem escolher?
A escolha entre essas duas abordagens depende diretamente das necessidades do sistema:
Quando Usar a Atribuição Via Método
Se o sistema exige flexibilidade, onde a cadeia de manipuladores pode mudar ou crescer dinamicamente, a atribuição via método se torna a mais indicada. Além disso, essa abordagem permite que o desenvolvedor facilmente adicione ou remova manipuladores com base nas condições de tempo de execução. Por exemplo, ela é ideal em cenários que demandam ajustes frequentes na cadeia de processamento.
Quando Usar a Atribuição Via Construtor
Em contrapartida, se o sistema prioriza estabilidade e consistência, e a cadeia permanece inalterada após sua definição, a atribuição via construtor é mais apropriada. Nesse contexto, o desenvolvedor utiliza essa abordagem em sistemas onde a cadeia de responsabilidade já é conhecida desde o início e deve se manter imutável durante toda a execução.
Exemplo de Implementações Concretas
Aqui estão dois exemplos de classes concretas para ilustrar como cada abordagem seria implementada:
Atribuição via Método:
class GerenteAprovacaoHandler extends Handler
{
public function handle(Request $request): ?string
{
if ($request->amount < 1000) {
return "Gerente: Aprovou\n";
} elseif ($this->nextHandler) {
return $this->nextHandler->handle($request);
}
return "Gerente: A solicitação requer aprovação adicional.";
}
}
Aqui, o próximo manipulador é configurado posteriormente, usando o método setNext()
:
$gerente = new GerenteAprovacaoHandler();
$diretor = new DiretorAprovacaoHandler();
$gerente->setNext($diretor);
Atribuição via Construtor:
class GerenteAprovacaoHandler extends Handler
{
public function __construct(Handler $nextHandler = null)
{
$this->nextHandler = $nextHandler;
}
public function handle(Request $request): ?string
{
if ($request->amount < 1000) {
return "Gerente: Aprovou\n";
} elseif ($this->nextHandler) {
return $this->nextHandler->handle($request);
}
return "Gerente: A solicitação requer aprovação adicional.";
}
}
Neste caso, o próximo manipulador é passado diretamente no construtor:
$diretor = new DiretorAprovacaoHandler();
$gerente = new GerenteAprovacaoHandler($diretor);
Então, o desenvolvedor precisa ajustar a classe abstrata Handler
de acordo com a forma de atribuição escolhida. Se, por exemplo, o desenvolvedor optar pela atribuição via método, ele deve então implementar o método setNext()
para encadear os manipuladores durante a execução. Por outro lado, caso o desenvolvedor prefira a atribuição via construtor, ele irá definir a cadeia no momento da criação dos objetos, o que garante uma estrutura fixa e imutável.
Portanto, ambas as abordagens oferecem suas vantagens. A escolha, no entanto, dependerá da flexibilidade ou rigidez que o sistema efetivamente exige.
Conclusão
Neste artigo, demonstrei como implementar o padrão Chain of Responsibility em PHP através de um sistema simples de processamento de solicitações de compra. Portanto, este padrão é extremamente útil para distribuir responsabilidades entre objetos em uma aplicação, facilitando a manutenção e a extensão do código.
A escolha entre configurar o próximo handler através de um método ou um construtor depende largamente de como você espera gerenciar a cadeia de handlers:
- Em primeiro lugar, use o método quando espera que a cadeia possa precisar de ajustes frequentes ou reconfiguração dinâmica.
- Por outro lado, use o construtor para uma configuração mais estática e segura, onde a cadeia não precisa ser alterada após a inicialização.
Em resumo, cada abordagem tem suas próprias vantagens e pode ser a melhor escolha dependendo das necessidades específicas da sua aplicação e da arquitetura de software. Compreender essas diferenças é fundamental para o desenvolvimento de aplicações robustas e flexíveis, contribuindo significativamente para a qualidade do design de software.