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/>";
}
}
design-patterns
Imran Omar Bukhsh
fonte
fonte
Respostas:
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).
Dessa forma, você pode criar várias implementações quackable:
E como fizemos a estratégia de acordo com os padrões, podemos adicionar mais tipos de charlatanismo:
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):
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:
Usar tudo isso é simples:
Espero que tudo isso faça sentido para você.
fonte
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.
fonte
RuberBall
? ORuberDuck
método também terá um rolo vazio?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
fly
equack
naDuck
classe, parece que você está dizendo que "Todos os patos podemfly
/quack
." O pior é que, dependendo do tipo de runtime da instância do duck que eu tenho,fly
ouquack
pode 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
Flyable
pato.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.A
Flyable
interface ajuda a estabelecer expectativas. Sim, seuquack
método padrão verificará se deve ou não, mas eu, como chamador, não tenho garantias de que seja seguro chamarquack
. Pense também no cenário com oRubberDucky
qual deve chiar em vez de grasnar. Ao substituir oquack
método, você deve saber para verificar a$quackable
variá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.
fonte
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.
fonte
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.
fonte
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
fonte