43

He estado leyendo el Patrón de estrategia y tengo una pregunta. He implementado una aplicación de consola muy básica a continuación para explicar lo que estoy preguntando.¿Patrón de estrategia sin declaraciones de "cambio"?

He leído que tener instrucciones de 'cambio' es una señal de alerta cuando se implementa el patrón de estrategia. Sin embargo, parece que no puedo evitar tener una declaración de cambio en este ejemplo. ¿Me estoy perdiendo de algo? Pude eliminar la lógica del Lápiz, pero mi Principal tiene ahora una declaración de cambio. Entiendo que podría crear fácilmente una nueva clase TriangleDrawer, y no tendría que abrir la clase Lápiz, lo cual es bueno. Sin embargo, necesitaría abrir Main para saber qué tipo de IDrawer pasar al Lápiz. ¿Es esto exactamente lo que se necesita hacer si confío en el usuario para la entrada? Si hay una manera de hacer esto sin la declaración de cambio, ¡me encantaría verlo!

class Program 
{ 
    public class Pencil 
    { 
     private IDraw drawer; 

     public Pencil(IDraw iDrawer) 
     { 
      drawer = iDrawer; 
     } 

     public void Draw() 
     { 
      drawer.Draw(); 
     } 
    } 

    public interface IDraw 
    { 
     void Draw(); 
    } 

    public class CircleDrawer : IDraw 
    { 
     public void Draw() 
     { 
      Console.Write("()\n"); 
     } 
    } 

    public class SquareDrawer : IDraw 
    { 
     public void Draw() 
     { 
      Console.WriteLine("[]\n"); 
     } 
    } 

    static void Main(string[] args) 
    { 
     Console.WriteLine("What would you like to draw? 1:Circle or 2:Sqaure"); 

     int input; 
     if (int.TryParse(Console.ReadLine(), out input)) 
     { 
      Pencil pencil = null; 

      switch (input) 
      { 
       case 1: 
        pencil = new Pencil(new CircleDrawer()); 
        break; 
       case 2: 
        pencil = new Pencil(new SquareDrawer()); 
        break; 
       default: 
        return; 
      } 

      pencil.Draw(); 

      Console.WriteLine("Press any key to exit..."); 
      Console.ReadKey(); 
     } 
    } 
} 

solución implementada se muestra a continuación (Gracias a todos los que respondieron!) Esta solución me llegó al punto donde lo único que tengo que hacer para utilizar un nuevo idraw objeto es crearlo.

Estrategia
public class Pencil 
    { 
     private IDraw drawer; 

     public Pencil(IDraw iDrawer) 
     { 
      drawer = iDrawer; 
     } 

     public void Draw() 
     { 
      drawer.Draw(); 
     } 
    } 

    public interface IDraw 
    { 
     int ID { get; } 
     void Draw(); 
    } 

    public class CircleDrawer : IDraw 
    { 

     public void Draw() 
     { 
      Console.Write("()\n"); 
     } 

     public int ID 
     { 
      get { return 1; } 
     } 
    } 

    public class SquareDrawer : IDraw 
    { 
     public void Draw() 
     { 
      Console.WriteLine("[]\n"); 
     } 

     public int ID 
     { 
      get { return 2; } 
     } 
    } 

    public static class DrawingBuilderFactor 
    { 
     private static List<IDraw> drawers = new List<IDraw>(); 

     public static IDraw GetDrawer(int drawerId) 
     { 
      if (drawers.Count == 0) 
      { 
       drawers = Assembly.GetExecutingAssembly() 
            .GetTypes() 
            .Where(type => typeof(IDraw).IsAssignableFrom(type) && type.IsClass) 
            .Select(type => Activator.CreateInstance(type)) 
            .Cast<IDraw>() 
            .ToList(); 
      } 

      return drawers.Where(drawer => drawer.ID == drawerId).FirstOrDefault(); 
     } 
    } 

    static void Main(string[] args) 
    { 
     int input = 1; 

     while (input != 0) 
     { 
      Console.WriteLine("What would you like to draw? 1:Circle or 2:Sqaure"); 

      if (int.TryParse(Console.ReadLine(), out input)) 
      { 
       Pencil pencil = null; 

       IDraw drawer = DrawingBuilderFactor.GetDrawer(input); 

       pencil = new Pencil(drawer); 
       pencil.Draw(); 
      } 
     } 
    } 
+1

Cambiar la declaración que puede causar que el principio de apertura/cierre sea violado posteriormente es malo. El patrón de estrategia ayuda a separar el enunciado del conmutador del lugar donde desea mantenerlo cerrado, pero aún tiene que ocuparse de elegir la estrategia/implementación en algún lugar, ya sea en la declaración de conmutación, si/else/if, o utilizando LINQ Where (que es mi favorito :-) Por cierto, el patrón de estrategia también ayuda a las pruebas unitarias al permitirte simular fácilmente la implementación de la estrategia. – kimsk

Respuesta

46

no es una solución anti-interruptor de la magia. Lo que sí hace es dar a modularizar su código para que en lugar de una gran lógica de conmutación y de negocios todo mezclado en una pesadilla para el mantenimiento

  • su lógica de negocio está aislado y abierto para la extensión
  • tiene opciones en cuanto a cómo crear sus clases concretas (ver patrones de fábrica, por ejemplo)
  • su código de infraestructura (el principal) puede ser muy limpia, libre de tanto

Por ejemplo - si se toma el interruptor en su método principal y creó una clase que aceptó el argumento de línea de comando nt y devolvió una instancia de IDraw (es decir encapsula ese interruptor) tu principal está limpio nuevamente y tu interruptor está en una clase cuyo único propósito es implementar esa elección.

+11

+1 - Siempre siento que Strategy and Factory van de la mano. –

+0

Gracias por ayudarme a entender qué es/no es el patrón de estrategia. He editado mi publicación para mostrar cómo terminé implementando esto. – JSprang

+0

@Brabster a veces me resulta difícil atrapar el momento adecuado cuando debe abandonar la instrucción switch y cambiar (juego de palabras intencionado) al patrón Stategy. Por ejemplo, si tiene 30 conmutadores muy pequeños y simples que en caso de ir para el patrón de Estrategia se convertirán en 30 clases adicionales, ¿es una buena razón para cambiar o dejar estos pequeños pedazos de código no tan limpio? ? – mmmm

13

No creo que su cambio aquí en su aplicación de demostración sea en realidad parte del patrón de estrategia en sí, solo se está utilizando para ejercitar las dos estrategias diferentes que ha definido.

La advertencia de "interruptores siendo un indicador rojo" se refiere a tener interruptores dentro de la estrategia; por ejemplo, si definió una estrategia "GenericDrawer" y lo hizo determinar si el usuario quería un SquareDrawer o un CircleDrawer internamente usando un interruptor contra un valor de parámetro, no obtendría el beneficio del patrón de estrategia.

15

La siguiente es una solución sobre ingeniería para su problema únicamente con el fin de evitar las declaraciones if/switch.

CircleFactory: IDrawFactory 
{ 
    string Key { get; } 
    IDraw Create(); 
} 

TriangleFactory: IDrawFactory 
{ 
    string Key { get; } 
    IDraw Create(); 
} 

DrawFactory 
{ 
    List<IDrawFactory> Factories { get; } 
    IDraw Create(string key) 
    { 
     var factory = Factories.FirstOrDefault(f=>f.Key.Equals(key)); 
     if (factory == null) 
      throw new ArgumentException(); 
     return factory.Create(); 
    } 
} 

void Main() 
{ 
    DrawFactory factory = new DrawFactory(); 
    factory.Create("circle"); 
} 
11

También puede deshacerse de if con la ayuda de un diccionario

Dictionary<string, Func<IDraw> factory> drawFactories = new Dictionary<string, Func<IDraw> factory>() { {"circle", f=> new CircleDraw()}, {"square", f=> new SquareDraw()}}(); 

Func<IDraw> factory; 
drawFactories.TryGetValue("circle", out factory); 

IDraw draw = factory(); 
+0

¡Me gusta! Lo extiendo para llenar el diccionario de fábricas de mi contenedor de inyección de dependencias, registrando varias implementaciones nombradas de la interfaz de fábrica. – Will

0

Un poco tarde, pero para cualquier persona que todavía está interesado en eliminar totalmente una sentencia condicional.

Cuestiones relacionadas