Solucionado (ver solução)
Solucionado
(ver solução)
13
respostas

Chain Of Responsability

No Curso de Design Patterns I, estou estudando o pattern 'Chain Of Responsability'. A implementação cria uma corrente de descontos por quantidades de itens ou por valor em reais, então na implementação tem a classe 'Desconto5Itens' , 'Desconto500Reais' e 'SemDesconto'. No calculo do desconto existe a classe 'CalculadorDeDescontos' que tenta aplicar os 3 tipos de descontos possíveis a um orçamento que são: 'Desconto5Itens', 'Desconto500Reais' e 'SemDescontos'.

A base das 2 primeiras classes de desconto é essa:

      class Desconto500Reais implements IDesconto {
        private $proximoDesconto;

        public function setProximo(IDesconto $proximo) {
          $this->proximo = $proximo;
        }

        public function desconta(Orcamento $orcamento) {
          if($orcamento->getValor() > 500) {
            return $orcamento->getValor() * 0.07;
          }
          else {
            return $this->proximoDesconto->desconta($orcamento);
          }
        }
      }

O que ocorre então é que cada objeto de desconto tem a referencia do proximo desconto e se ele não atender o desconto no método 'desconta' passa para o proximo desconto.

A minha questão é:

Não seria mais fácil o objeto desconto antes de passar a bola para o próximo desconto testar se o próximo desconto existe? Se o objeto atual testar e proximo estiver la ele passa, caso contrario ele já retorna zero porque ele é o último da corrente .

A classe 'SemDesconto' no caso eu acho meio inutil porque sempre retornará Zero e ainda por cima será obrigada a implementar um método vazio 'setProximo'.

O que vocês dizem sobre esta questão?

Obrigado.

13 respostas

Oi Alexandre, tudo bom?

Sua ideia faz todo sentido. Fazemos a verificação se existe alguma atribuição em proximo desconto, na propria classe que define um desconto e assim evitamos a criação de uma classe a mais só pra indicar o fim da cadeia.

Entretanto, nesse mesmo cenário, estariamos criando uma verificação a mais pra cada elo da corrente, certo? Cada vez que um desconto for calculado faremos um if para verificar se há um proximo. A ideia da cadeia de responsabilidades era justamente eliminar verificações e estariamos com o mesmo custo de processamento das condicionais já que as verificações seriam feitas elo por elo. Talvez até mais já que haveria a navegação entre as referencias.

Repare que nestes casos não existe muito certo e errado. Conseguimos implementar a cadeia de diversas formas e é bem legal pensar em novas implementações também =)

Espero ter ajudado, abraço e bons estudos.

Oi André, concordo com você também, mas se for pensar que se torna uma obrigação da implementação ter o objeto 'SemDesconto', talvez seja melhor esse teste de proxima referencia.

Enfim, eu to estudando SOLID também e tem muitos conceitos legais e pra aplicar os conceitos, vai depender muito da situação. Não existe receita de bola em implementações hehehehe.

É isso aí,

valeu por responder.

solução!

Fala, Alexandre.

Concordo que o SemDesconto não deveria precisar implementar o setProximo, pois isso inclusive viola o Princípio de Segregação de Interface.

Porém como o André falou, não tendo esta classe, você precisaria ter um if a mais em todas as classes de desconto.

Minha solução seria ter um construtor para os descontos, onde ele recebe o próximo.

Abraços e bons estudos.

Fala Carlos, pois é eu pensei nesse princípio de segregação quando eu vi essa classe, por isso fiquei com isso na cabeça.

A sua ideia resolve essa questão. Algo como isso:

$semDesconto = new Desconto(null); $desconto5Itens = new Desconto($semDesconto); $desconto500Reais = new Desconto($desconto5Itens);

$desconto500Reais->desconta($orcamento);

No seu exemplo da pra eliminar da interface o metodo 'setProximo'.

Legal Carlos, obrigado por responder.

Abraço e bons estudos também.

Rapazeada testei a solução do Carlos e realmente funcionou. A única treta que deu é que no construtor dos descontos eu não posso especificar que o parametro é um Desconto como no exemplo:

class SemDesconto implements Desconto {

private $proximoDesconto;

function __construct(Desconto $proximoDesconto) { $this->proximoDesconto = $proximoDesconto; }

public function desconta(Orcamento $orcamento) { echo "Não aplicou desconto."; return 0; } }

Se eu fizer como ta acima da esse erro:

Catchable fatal error: Argument 1 passed to SemDesconto::__construct() must implement interface Desconto, null given, called in /Applications/XAMPP/xamppfiles/htdocs/dp1/indexDesconto.php on line 24 and defined in /Applications/XAMPP/xamppfiles/htdocs/dp1/SemDesconto.php on line 6

A forma que achei de contornar esse erro foi não especificar o tipo da classe do parametro desta forma:

function __construct($proximoDesconto) { $this->proximoDesconto = $proximoDesconto; }

Como as váriaveis do PHP aceitam tudo, hehehehe, ele não deu pau. Mas não sei se é uma boa prática ou se tem uma melhor forma de fazer.

O que vocês acham?

PS: Sou um pequeno gafanhoto no PHP! heheehehe.

Fala, Alexandre!

Minha ideia era realmente passar a classe SemDesconto como parâmetro, ao invés de nulo, pra que não se fizesse necessária a verificação se $proximoDesconto é nulo ou não, entende?

Oi Carlos, exato, foi o que eu fiz, só que o ultimo objeto é o 'SemDesconto', então pra você intanciar ele terá que passar null para o construtor porque não existe próximo depois dele, entendeu?

veja abaixo que vai entender:

$semDesconto = new SemDesconto(null);
$maiorQue500Reais = new MaiorQue500Reais($semDesconto);
$descontoMais5Itens = new DescontoMaisDe5Itens($maiorQue500Reais);

Então nesse caso aí não existe verificacao de proximo e somente a regra normal, como esse exemplo aqui:

class MaiorQue500Reais implements Desconto {

    private $proximoDesconto;

    function __construct($proximoDesconto) {
        $this->proximoDesconto = $proximoDesconto;
    }

    public function desconta(Orcamento $orcamento) {
        if ($orcamento->getValor() > 500) {
            echo "<br>Aplicou desconto para orçamento maior que R$ 500,00 de 7%.</br>";
            return $orcamento->getValor() * 0.07;
        } else {
            return $this->proximoDesconto->desconta($orcamento);
        }
    }

}

É isso aí.

Alexandre, a classe Sem Desconto não precisa receber nada no construtor. E você não quebra o contrato com a interface Desconto, porque interfaces não podem definir construtores.

Carlos, você tem razão novamente. Alterei aqui e funcionou também. A única coisa mesmo que não deu certo é forçar o construtor nas classes que precisam de um objeto Desconto que deve ser um objeto Desconto como abaixo:

public function desconta(Orcamento $orcamento);

Só funciona assim: public function desconta($orcamento);

É iss aí. Valeu.

Por que não de ucerto, Alexandre?

A classe SemDesconto implementa a interface Desconto, então você pode passá-la no construtor com a assinatura public function __construct(Desconto $proximoDesconto). É só a classe SemDesconto não receber nada no construtor.

Embora seja possível, não gosto de trabalhar sem type hinting. Em meus projetos pessoais, uso PHP 7.1 e especifico todos os tipos de argumentos e retorno. Diminui a chance de erros.

Carlos, eu modifiquei aqui e deu certo agora. Então a classe 'SemDesconto' está assim:

class SemDesconto implements Desconto {

public function desconta(Orcamento $orcamento) { echo "Não aplicou desconto."; return 0; } }

Agora não tem construtor nela e implementa de 'Desconto' o metodo 'desconta'.

Para as outras classes, alem de implementar o método 'desconta', tem o construtor recebendo um objeto 'Desconto'. Neste caso o construtor está sendo forçado.

Não tava dando certo porque a classe 'SemDesconto' tava com um construtor desnecessário, como você inclusive falou no ultimo post.

É isso aí. Obrigado pela ajuda.

Show de bola, Alexandre.

:-D

Só uma dica pra futuros posts aqui no fórum. Quando for postar códigos, coloca eles entre 3 crases ( ` ), que eles vão ficar assim:

<?php

// codigo fonte

Eu até cheguei a editar alguns dos seus comentários nessa pergunta pra poder visualizar melhor o código. Rsrsrs

Abraços e bons estudos, Alexandre.

Opa Carlos, valeu!

Abraço e bons estudos tb.