He estado interesado en los patrones de diseño por un tiempo y comencé a leer "Head First Design Patterns". Comencé con el primer patrón llamado patrón de 'Estrategia'. Revisé el problema descrito en las imágenes a continuación y primero traté de proponer una solución para poder comprender realmente la importancia del patrón.
Entonces mi pregunta es por qué mi solución al siguiente problema no es lo suficientemente buena. ¿Cuáles son los puntos buenos / malos de mi solución frente al patrón? ¿Qué hace que el patrón sea claramente la única solución viable?
MI SOLUCIÓN
Clase para padres: 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/>");
}
}
}
?>
CLASE DE HERENCIA: MallardDuck
<?php
class MallardDuck extends Duck
{
function MallardDuck()
{
$this->quackable = 1;
$this->swimmable = 1;
}
function display()
{
echo "A Mallard Duck Looks Like This<BR/>";
}
}
?>
CLASE DE HERENCIA: 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
fuente
fuente
Respuestas:
Su código se romperá cuando, por ejemplo, los patos graznen con sonidos diferentes. El booleano solo conducirá a otros booleanos a declaraciones if realmente peludas.
Sin embargo, el patrón de estrategia es simple. Tome en este caso el método quack y colóquelo en su propia clase o interfaz. Una interfaz en php sería una clase que no contiene ninguna implementación que no sean métodos que sean apéndices (es decir, no sucede nada cuando se los llama).
De esa manera, puede crear varias implementaciones grapables:
Y debido a que lo hicimos con un patrón de estrategia inteligente, podemos agregar más tipos de "charlatanería":
Lo mismo se puede aplicar a los métodos de vuelo y natación para la clase Duck. La forma en que implementa una clase Duck con un quackable sería así (la interfaz quackable se suministra a través del constructor, que es algo así como funciona la inyección de dependencia):
Implementar el pato mallard y el pato señuelo será simple, ya que solo necesita proporcionar qué tipo de graznidos debe hacer el pato:
Usarlo todo es simple:
Espero que todo esto tenga sentido para ti.
fuente
En primer lugar, su problema no tiene nada que ver con el patrón de estrategia. La idea del patrón de estrategia es factorizar la responsabilidad de un determinado comportamiento en una clase diferente. De esa manera, puede conectar diferentes comportamientos en una instancia en tiempo de ejecución.
Ahora su solución funciona razonablemente bien para el escenario en el que se encuentra, pero el escenario es solo un ejercicio, que está bastante lejos de los problemas del mundo real.
Si utiliza esta técnica para hacer frente a grandes proyectos, corre el riesgo de terminar con 5-10 capas de herencia, donde cada subclase se combina con más y más banderas. Tal código es extremadamente frágil y verboso. Jugar con el estado interno de todas las superclases no es exactamente la forma de manejar las cosas, porque desdibuja la separación de las preocupaciones.
Una solución que utiliza interfaces es significativamente más limpia, más robusta y, por lo tanto, será más fácil de mantener con el tiempo. Intenta no hacer un pato base general para todo uso, sino más bien hacer abstracciones claras de los diferentes aspectos de los diferentes patos. Hace poco hice una publicación en el blog sobre este tema, puede que te resulte útil.
fuente
RuberBall
? ¿TendráRuberDuck
también un método de rollo vacío?Ah, buena pregunta. No creo que sea una buena práctica tener variables miembro que cambien la funcionalidad. Principalmente, el problema con esta solución es que, dado que expone los métodos
fly
yquack
en laDuck
clase, parecería que está diciendo que "Todos los patos puedenfly
/quack
". ¿Cuál es peor es que, dependiendo del tipo de tiempo de ejecución de la instancia de pato que tengo,fly
oquack
puede o no puede hacer nada. Esto puede conducir a un código muy confuso.Donde, como en el ejemplo que da el libro, cuando espera un pato que puede volar, puede escribir (o probar, ya que está usando un lenguaje dinámico) si fuera un
Flyable
pato.Si cada vez que tengo un pato, antes de llamarlo
quack
, tengo que decidir si tengo que verificar si la variable miembro volador está establecida en verdadero o no, lo que conduciría a una gran duplicación de código.La
Flyable
interfaz ayuda a establecer expectativas. Sí, suquack
método predeterminado verificará si debe o no, pero yo, como la persona que llama, no tengo garantías de que sea seguro llamarquack
. También piense en el escenario con elRubberDucky
que debería chirriar en lugar de graznar. Cuando anula elquack
método, debe saber verificar la$quackable
variable miembro antes de realizar cualquier acción.Algo más que creo que hace que esta solución no sea clara es que, dado que está utilizando un lenguaje dinámico como PHP, dificulta el establecimiento de expectativas sobre los tipos de entrada. (No es que haya algo malo en esto, pero un lenguaje tipeado estáticamente podría facilitar su comprensión).
Espero que esto ayude y tenga sentido.
fuente
Su código se volverá desordenado rápidamente en el caso de que tenga, por ejemplo, 10 tipos diferentes de patos que tienen diferentes combinaciones de, por ejemplo, 3 variantes diferentes de volar, graznar y nadar.
Personalmente, descubrí que el verdadero valor del patrón de estrategia se hizo evidente cuando comencé a escribir pruebas unitarias y a usar la inyección de dependencia. Si no lo está utilizando, tendrá muchos problemas para administrar las dependencias y, por lo tanto, crear objetos en sus pruebas.
Llegará a situaciones en las que crear un pato se complicará, entonces es posible que desee escribir una prueba unitaria para algunos de los métodos de graznido o vuelo, pero no puede hacerlo sin crear un pato en su prueba.
fuente
Tener un estado booleano para almacenar si los patos pueden graznar / volar, es una solución muy específica para el problema particular que enfrenta en el diseño de su clase y puede no ser aplicable a otros casos en los que el patrón de estrategia es apropiado.
Creo que su enfoque 'graznable' hace que el código sea un poco más complicado. Es una cosa más que usted y los usuarios de su clase deben tener en cuenta de lo que sería necesario si estuviera utilizando un patrón de estrategia.
Finalmente, como su código de muestra no muestra ningún ejemplo de cómo anularía los métodos 'quack' y 'fly', no está notando un gran beneficio de usar el patrón de estrategia: reducir la duplicación de código. ¿Qué harías si dieras una docena de clases de pato diferentes que, entre sí, tienen 3 comportamientos diferentes de 'volar'? Usando su enfoque, probablemente se encontrará cortando y pegando, y luego posiblemente extrayendo el código duplicado en clases estáticas 'auxiliares'. El patrón de estrategia es mucho más limpio.
fuente
Una buena demostración visual siempre vale más que mil palabras.
John Lindquist está haciendo un gran trabajo para grabar screencasts sobre diferentes patrones de diseño. Puedes encontrar sus 50 centavos sobre este patrón en particular (y mucho más) en su blog
fuente