Solucionado (ver solução)

Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

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.