Comprender mejor el patrón de diseño de 'estrategia'

8

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/>";
 }
}
Imran Omar Bukhsh
fuente
su solución no es exactamente lo que se quiere decir (o incluso una solución al uno) en el libro
Mauris
¿Puede ayudar a explicar mejor
Imran Omar Bukhsh
55
No estoy seguro de que pueda escanear páginas de los patrones de Head First Design y simplemente ponerlas en la web.
Ladislav Mrnka
1
Esto parece una revisión de código .
Steven Jeuris
2
@Imran Como se menciona en Amazon, esas páginas son material protegido por derechos de autor. No se pueden publicar libremente aquí. Edite la pregunta para indicar el problema en sus propias palabras.
Adam Lear

Respuestas:

6

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).

class Quackable {
    function quack() {}
}

De esa manera, puede crear varias implementaciones grapables:

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

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

Y debido a que lo hicimos con un patrón de estrategia inteligente, podemos agregar más tipos de "charlatanería":

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

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):

class Duck {
    protected $quackable;

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

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

Implementar el pato mallard y el pato señuelo será simple, ya que solo necesita proporcionar qué tipo de graznidos debe hacer el pato:

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
    }
}

Usarlo todo es simple:

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

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

Espero que todo esto tenga sentido para ti.

Spoike
fuente
El código puede tener errores, ha pasado un tiempo desde que codifiqué algo en php. :-P
Spoike
Además, el OP necesita pasar algunas páginas más para llegar al patrón de Estrategia y al principio de "composición antes de la herencia" en ese libro. :) Las páginas escaneadas aparecen con el problema de la herencia.
Spoike
gracias por la explicación
Imran Omar Bukhsh
4

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.

back2dos
fuente
1
+1 pero debo decir que este es el ejemplo dado para el Patrón de estrategia en el libro, que es un libro excelente. Desafortunadamente, no ha mostrado las siguientes páginas donde se diseñan los comportamientos de mosca, graznido, etc. y luego se conectan a los patos (es decir, la parte que aplica el patrón de estrategia al problema), pero está en el libro.
Alb
¡He agregado la siguiente página del libro para mostrar por qué la solución de interfaz es un total, no, no!
Imran Omar Bukhsh
@Alb: sí, son aproximadamente 13 páginas
Imran Omar Bukhsh
@ Imran: Lo siento, pero el autor del libro está simplemente equivocado. Las interfaces no dicen nada sobre la implementación. La clase puede implementarse mediante composición o delegación (el patrón de estrategia es un caso especial). Sin embargo, dar un método de vuelo a un pato que no puede volar, solo porque no puede resolverlo, de lo contrario, es un diseño deficiente. Y no escala. ¿Y si tú también tienes RuberBall? ¿Tendrá RuberDucktambién un método de rollo vacío?
back2dos
3

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 flyy quacken la Duckclase, parecería que está diciendo que "Todos los patos pueden fly/ quack". ¿Cuál es peor es que, dependiendo del tipo de tiempo de ejecución de la instancia de pato que tengo, flyo quackpuede 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 Flyablepato.

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.

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

La Flyableinterfaz ayuda a establecer expectativas. Sí, su quackmétodo predeterminado verificará si debe o no, pero yo, como la persona que llama, no tengo garantías de que sea seguro llamar quack. También piense en el escenario con el RubberDuckyque debería chirriar en lugar de graznar. Cuando anula el quackmétodo, debe saber verificar la $quackablevariable 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.

Cifrar
fuente
1

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.

Alba
fuente
1

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.

richeym
fuente
1

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

Pierre Watelet
fuente