Ir ao conteúdo

Padrão de Projeto Chain of Responsability em PHP com exemplo

O Padrão de Projeto Chain of Responsability, através de uma cadeia de responsabilidades, consegue evitar dependências entre um objeto receptor e um objeto solicitante. Ou seja, um objeto fica como responsável por decidir se vai processar alguma mensagem ou a passar a responsabilidade para o próximo objeto.

Diagrama de Classe UML Chain of Responsability
Diagrama de Classe UML Chain of Responsability

A intenção do padrão Chain of Responsability está bem clara. O objeto receptor, ao receber a solicitação do remetente, vai castateando entre outros objetos até que um saiba como tratar a solicitação. Enquanto um não atender a expectativa, vai descendo e descendo. Ou seja, o objeto tratador da solicitação não é especificado explicitamente. 

Pense no seguinte problema. Imagine que você está modelando um sistema de vendas e tem um requisito de negócio que dependendo do valor da venda, precisará de uma autorização especial. Exemplo, se o calor for menor que 3 mil, o próprio vendedor pode aprovar. Entre 3 e 30 mil, precisa da aprovação do gerente. Caso o valor seja superior a este valor máximo, precisa ser aprovado pelo Diretor.

Exemplo da cascata de responsabilidade
Exemplo da cascata de responsabilidade

Vamos para os códigos que fica mais divertido colocar o Padrão Chain de Responsability em ação. Primeiro vamos definir o nosso contrato do método responsável pela aprovação da venda e do nosso Sale que será nosso Value Object.

<?php

declare(strict_types=1);

namespace DesignPattern\Behavioral\ChainOfResponsability;

namespace Growthdev\DesignPatterns\Behavioral\ChainOfResponsability;

interface SaleHandler 
{
    public function processSale(Sale $sale): void;
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\ChainOfResponsability;

final class Sale
{
    public readonly float $price;

    public function __construct(float $price)
    {
        $this->price = $price;
    }
}

Para deixar este exemplo bem mais próximo do diagrama UML original, modelei apenas adicionando a interface Sale handler como contrato para processar as vendas. De resto, você consegue perceber o auto-relacionamento de recursividade da classe abstrata ApproveHandler

Diagrama UML com exemplo de uso do Chain of Responsability Pattern
Diagrama UML com exemplo de uso do Chain of Responsability Pattern
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\ChainOfResponsability;

abstract class ApproveHandler implements SaleHandler
{
    private ?ApproveHandler $nextHandler = null;

    public final function setNext(ApproveHandler $nextHandler): ApproveHandler
    {
        $this->nextHandler = $nextHandler;
        return $nextHandler;
    }

    public function processSale(Sale $sale): void
    {
        if ($this->nextHandler) {
            $this->nextHandler->processSale($sale);
        }
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\ChainOfResponsability;

// vendedor
final class SellerHandler extends ApproveHandler
{
    public function processSale(Sale $sale): void
    {        
        if ($sale->price < 3_000) {
            printf("Sale approved by seller with price %.2f\n", $sale->price);
        } else {
            parent::processSale($sale);
        }
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\ChainOfResponsability;

// gerente
final class ManagerHandler extends ApproveHandler
{
    public function processSale(Sale $sale): void
    {
        if ($sale->price >= 3_000 && $sale->price < 30_000) {
            printf("Sale approved by manager with price %.2f\n", $sale->price);
        } else {
            parent::processSale($sale);
        }
    }
}
<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Behavioral\ChainOfResponsability;

// Diretor
final class DirectorHandler extends ApproveHandler
{
    public function processSale(Sale $sale): void
    {
        if ($sale->price >= 30_000) {
            printf("Sale approved by director with price %.2f\n", $sale->price);
        } else {
            parent::processSale($sale);
        }
    }
}

Veja na implementação dos testes, como o padrão Chain of Responsibility deixa nosso código limpo e muito organizado. Obviamente, quando você memoriza o comportamento deste padrão, você vai ver que muitos frameworks web utilizam largamente este pattern. Se você souber de algum exemplo, deixe aqui nos comentários.

<?php

declare(strict_types=1);

namespace Growthdev\DesignPatterns\Tests\Behavioral\ChainOfResponsability;

use Growthdev\DesignPatterns\Behavioral\ChainOfResponsability\DirectorHandler;
use Growthdev\DesignPatterns\Behavioral\ChainOfResponsability\ManagerHandler;
use Growthdev\DesignPatterns\Behavioral\ChainOfResponsability\Sale;
use Growthdev\DesignPatterns\Behavioral\ChainOfResponsability\SellerHandler;
use PHPUnit\Framework\TestCase;

final class ChainOfResponsabilityTest extends TestCase
{
    public function testExpectOfApproveBySeller()
    {
        $seller = new SellerHandler;
        $seller->setNext(new ManagerHandler)
            ->setNext(new DirectorHandler);

        // Request
        $sale = new Sale(2_999.99);
        $seller->processSale($sale);

        $this->expectOutputString("Sale approved by seller with price 2999.99\n");
    }

    public function testExpectOfApproveByManager()
    {
        $seller = new SellerHandler;
        $seller->setNext(new ManagerHandler)
            ->setNext(new DirectorHandler);

        // Request
        $sale = new Sale(3_999.99);
        $seller->processSale($sale);

        $this->expectOutputString("Sale approved by manager with price 3999.99\n");
    }

    public function testExpectOfApproveByDirector()
    {
        $seller = new SellerHandler;
        $seller->setNext(new ManagerHandler)
            ->setNext(new DirectorHandler);

        // Request
        $sale = new Sale(30_000.01);
        $seller->processSale($sale);

        $this->expectOutputString("Sale approved by director with price 30000.01\n");
    }
}

Se você ainda não está acompanhando, este artigo é mais de da série de resumo dos padrões de projetos. Você também pode encontrar todos os códigos de todos os exemplos no meu Github:

https://github.com/growthdev-repo/design-patterns

Se você estiver gostando desta série e quiser ajudar, basta compartilhar com seus amigos e colegas de trabalho para que estes artigos cheguem ao máximo de pessoas possíveis. Até o próximo artigo!

Confiança Sempre!!!

Fontes:

Publicado emDesign PatternPadrões de ProjetosPHP

Seja o primeiro a comentar

Deixe um comentário

O seu endereço de e-mail não será publicado.