Compreenda melhor o padrão de design da 'estratégia'

8

Estou interessado em padrões de design há algum tempo e comecei a ler "Head First Design Patterns". Comecei com o primeiro padrão chamado padrão de 'Estratégia'. Passei pelo problema descrito nas imagens abaixo e primeiro tentei propor uma solução para que eu pudesse entender a importância do padrão.

Portanto, minha pergunta é: por que minha solução para o problema abaixo não é boa o suficiente. Quais são os pontos bons / ruins da minha solução versus o padrão? O que torna o padrão claramente a única solução viável?

MINHA SOLUÇÃO

Classe pai: PATO

<?php
class Duck
{
 public  $swimmable;
 public  $quackable;
 public  $flyable;

 function display()
 {
  echo "A Duck Looks Like This<BR/>";
 }

 function  quack()
 {
  if($this->quackable==1)
  {
   echo("Quack<BR/>");
  }
 }

 function swim()
 {
  if($this->swimmable==1)
  {
   echo("Swim<BR/>");
  }
 }

 function  fly()
 {
  if($this->flyable==1)
  {
   echo("Fly<BR/>");
  }
 }


}
?>

Classe herdada: MallardDuck

<?php
class MallardDuck extends Duck
{
 function MallardDuck()
 {
  $this->quackable = 1;
  $this->swimmable = 1;
 }

 function display()
 {
  echo "A Mallard Duck Looks Like This<BR/>";
 }
}
?>

Classe herdada: WoddenDecoyDuck

<?php
class WoddenDecoyDuck extends Duck
{
 function woddendecoyduck()
 {
  $this->quackable = 0;
  $this->swimmable = 0;
 }

 function display()
 {
  echo "A Wooden Decoy Duck Looks Like This<BR/>";
 }
}
Imran Omar Bukhsh
fonte
sua solução não é exatamente o que se entende (ou mesmo uma solução para o um) sobre o livro
mauris
Você pode ajudar a explicar melhor
Imran Omar Bukhsh
5
Não tenho certeza de que você pode digitalizar páginas dos padrões do Head First Design e simplesmente colocá-las na web.
Ladislav Mrnka
1
Parece uma revisão de código .
9118 Steven Jeuris
2
@Imran Como mencionado na Amazon, essas páginas são material com direitos autorais. Eles não podem ser postados gratuitamente aqui. Edite a pergunta para indicar o problema com suas próprias palavras.
Adam Lear

Respostas:

6

Seu código será quebrado quando, por exemplo, patos grasnam com sons diferentes. O booleano só levará outros booleanos a declarações if muito peludas.

O padrão de estratégia é simples. Pegue neste exemplo o método charlatão e coloque-o em sua própria classe ou interface. Uma interface no php seria uma classe que não contém nenhuma implementação além de métodos que são stubs (ou seja, nada acontece quando você os chama).

class Quackable {
    function quack() {}
}

Dessa forma, você pode criar várias implementações quackable:

class DuckQuack extends Quackable {
    function quack() {
        return "Quack!";
    }
}

class SilentQuack extends Quackable {
    function quack() {
        return ""; 
            // The decoy duck can't quack. It is silent.
    }
}

E como fizemos a estratégia de acordo com os padrões, podemos adicionar mais tipos de charlatanismo:

class DoubleQuack extends Quackable {
    function quack() {
        return "Quackety-quack!"
    }
}

O mesmo pode ser aplicado aos métodos de voar e nadar para a classe Duck. A maneira como você implementa uma classe Duck com um quackable seria assim (a interface quackable é fornecida por meio do construtor, que é uma espécie de como a injeção de dependência funciona):

class Duck {
    protected $quackable;

    // The constructor
    function __construct($quackable) {
        $this->quackable = quackable;
    }

    function quack() {
        echo $this->quackable->quack();
    }
}

A implementação do pato-real e do pato-isca será simples, pois você só precisa fornecer que tipo de charlatão o pato deve fazer:

class MallardDuck extends Duck {
    function __construct() {
        parent::__construct(new DuckQuack());
           // we construct the mallard duck with a duck quack
    }
}

class DecoyDuck extends Duck {
    function __construct() {
        parent::__construct(new SilentQuack());
           // we construct the decoy duck with a silent quack
    }
}

Usar tudo isso é simples:

$duck1 = new MallardDuck();
$duck2 = new DecoyDuck();

$duck1.quack(); // echoes out "Quack!"
$duck2.quack(); // echoes out "" (because decoy ducks don't quack)

Espero que tudo isso faça sentido para você.

Spoike
fonte
O código pode ter erros, já faz um tempo desde que eu codifiquei algo em php. :-P
Spoike
Além disso, o OP precisa virar mais algumas páginas para chegar ao padrão de estratégia e ao princípio "composição antes da herança" nesse livro. :) As páginas digitalizadas antecedem o problema da herança.
Spoike 30/01
thanx para a explicação
Imran Omar Bukhsh
4

Primeiro de tudo, seu problema não tem nada a ver com o padrão de estratégia. A idéia do padrão de estratégia é fatorar a responsabilidade por um determinado comportamento em uma classe diferente. Dessa forma, você pode conectar diferentes comportamentos em uma instância em tempo de execução.

Agora sua solução funciona razoavelmente bem para o cenário em que você está, mas o cenário é apenas um exercício, que está muito longe dos problemas do mundo real.
Se você usar essa técnica para enfrentar grandes projetos, corre o risco de acabar com 5 a 10 camadas de herança, onde cada subclasse é acoplada a mais e mais sinalizadores. Esse código é extremamente frágil e verboso. Brincar com o estado interno de todas as super classes não é exatamente a maneira de lidar com essas coisas, porque confunde a separação de preocupações.

Uma solução usando interfaces é significativamente mais limpa, mais robusta e, portanto, será mais sustentável ao longo do tempo. Tente não fazer um pato básico para todos os fins, mas faça abstrações claras dos diferentes aspectos de diferentes patos. Recentemente, fiz um post sobre este assunto, que você pode achar útil.

back2dos
fonte
1
+1, mas devo dizer que este é o exemplo dado para o Padrão de Estratégia no livro, que é um excelente livro. Infelizmente, ele não mostrou nas próximas páginas onde os comportamentos de mosca, charlatão etc. são projetados e depois conectados aos patos (isto é, a parte que aplica o padrão de estratégia ao problema), mas está no livro.
Alb
Eu adicionei a próxima página do livro para mostrar por que a solução de interface é um total não, não!
Imran Omar Bukhsh
@Alb: sim, são 13 páginas
Imran Omar Bukhsh
@Imran: Desculpe, mas o autor do livro está completamente errado. As interfaces não dizem nada sobre implementação. A turma pode ser implementada por meio de composição ou delegação (o padrão de estratégia é um caso especial). No entanto, dar um método de mosca a um pato que não pode voar, só porque você não pode resolvê-lo, caso contrário, é um projeto ruim. E não escala. E se você também tiver RuberBall? O RuberDuckmétodo também terá um rolo vazio?
back2dos
3

Ah, boa pergunta. Eu não acho que é uma prática muito boa ter variáveis ​​de membro que alternam funcionalidade. O problema principal dessa solução é que, como você expõe os métodos flye quackna Duckclasse, parece que você está dizendo que "Todos os patos podem fly/ quack." O pior é que, dependendo do tipo de runtime da instância do duck que eu tenho, flyou quackpode ou não fazer nada. Isso pode levar a um código muito confuso.

Onde, como no exemplo do livro, quando você espera um pato que possa voar, você pode digitar (ou testar, já que está usando uma linguagem dinâmica) se for um Flyablepato.

Se toda vez que eu tinha um pato, antes de chamá quack-lo, tenho que decidir se devo verificar se a variável de membro flyable está ou não definida como true, o que levaria a muita duplicação de código.

if($duck->quackable) $duck->quack();

A Flyableinterface ajuda a estabelecer expectativas. Sim, seu quackmétodo padrão verificará se deve ou não, mas eu, como chamador, não tenho garantias de que seja seguro chamar quack. Pense também no cenário com o RubberDuckyqual deve chiar em vez de grasnar. Ao substituir o quackmétodo, você deve saber para verificar a $quackablevariável de membro antes de executar qualquer ação.

Outra coisa que acho que torna essa solução pouco clara é que, como você está usando uma linguagem dinâmica como PHP, torna difícil definir expectativas sobre os tipos de entrada. (Não que exista algo errado nisso, mas uma linguagem com estática pode facilitar a compreensão.)

Espero que isso ajude e faça sentido.

Cifra
fonte
1

Seu código ficará confuso rapidamente, caso você diga 10 tipos diferentes de patos, todos com combinações diferentes, digamos 3 variantes diferentes de mosca, charlatão e natação.

Pessoalmente, descobri que o verdadeiro valor do padrão de estratégia ficou claro quando comecei a escrever testes de unidade e a usar injeção de dependência. Se você não o estiver usando, terá muitos problemas para gerenciar dependências e, assim, criar objetos em seus testes.

Você chegará a situações em que criar um pato se tornará complicado, e poderá escrever um teste de unidade para alguns dos métodos de charlatanismo ou voo, mas não poderá fazê-lo sem criar um pato em seu teste.

Alva
fonte
1

Ter um estado booleano para armazenar se os patos podem grasnar / voar é uma solução muito específica para o problema específico que você está enfrentando no design de sua classe e pode não ser aplicável a outros casos em que o padrão de estratégia é apropriado.

Eu acho que sua abordagem 'quackable' torna o código um pouco mais complicado. É mais uma coisa que você e os usuários de sua classe precisam estar cientes do que seria necessário se você estivesse usando um padrão de estratégia.

Finalmente, como seu código de exemplo não mostra nenhum exemplo de como você substituiria os métodos 'charlatão' e 'fly', você não está percebendo um enorme benefício em usar o padrão de estratégia - reduzindo a duplicação de código. O que você faria com uma dúzia de classes diferentes de patos que, entre si, têm 3 comportamentos diferentes de 'voar'? Usando sua abordagem, você provavelmente se encontrará recortando e colando e possivelmente extraindo o código duplicado em classes estáticas 'auxiliares'. O padrão de estratégia é muito mais limpo.

richeym
fonte
1

Uma boa demonstração visual sempre vale mais que mil palavras.

John Lindquist está fazendo um ótimo trabalho para gravar screencasts sobre diferentes padrões de design. Você pode encontrar os 50 centavos dele sobre esse padrão específico (e muito mais) em seu blog

Pierre Watelet
fonte