43

Ho letto un po 'sul modello di strategia e ho una domanda. Ho implementato un'applicazione di console molto di base qui sotto per spiegare cosa sto chiedendo.Schema della strategia senza istruzioni 'switch'?

Ho letto che avere istruzioni 'switch' è una bandiera rossa quando si implementa il modello di strategia. Tuttavia, non riesco a staccarmi dall'avere un'istruzione switch in questo esempio. Mi sto perdendo qualcosa? Sono stato in grado di rimuovere la logica dalla matita , ma il mio principale ha ora un'istruzione switch in esso. Capisco che potrei facilmente creare una nuova classe TriangleDrawer e non aprire la classe Pencil, che è buona. Tuttavia, avrei bisogno di aprire Main in modo da sapere quale tipo di ID2IDrawer passare alla matita . Questo è solo ciò che deve essere fatto se mi affido all'utente per l'input? Se c'è un modo per farlo senza l'istruzione switch, mi piacerebbe vederlo!

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(); 
     } 
    } 
} 

soluzione implementata mostrato di seguito (Grazie a tutti coloro che hanno risposto!) Questa soluzione mi ha fatto al punto in cui l'unica cosa che devo fare per utilizzare un nuovo idraw oggetto è quello di crearlo.

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

L'istruzione di commutazione che può causare la violazione del principio di apertura/chiusura in seguito è errata. Il modello di strategia aiuta a separare l'affermazione dello switch dal luogo in cui si desidera mantenerlo chiuso, ma devi ancora scegliere la strategia/implementazione in cui sia l'istruzione switch, if/else/if o l'uso di LINQ Where (che è il mio preferito :-) A proposito, il modello strategico aiuta anche il collaudo delle unità permettendoti di simulare facilmente l'implementazione della strategia. – kimsk

risposta

46

La strategia non è una soluzione magica anti-switch. Quello che fa è dare modularizzare il codice in modo che invece di una grande logica di interruttore e di business tutti mescolati in un incubo di manutenzione

  • la logica di business è isolato e aperto per l'estensione
  • avete opzioni come per quanto si creano le classi concrete (vedere i modelli di fabbrica, per esempio)
  • il tuo codice di infrastruttura (il vostro principale) può essere molto pulito, privo di entrambe

per esempio - se si ha l'interruttore nel metodo principale e creato una classe che ha accettato la linea di comando argume nt e ha restituito un'istanza di IDraw (ad es. incapsula quell'interruttore) il tuo principale è di nuovo pulito e il tuo interruttore si trova in una classe il cui unico scopo è quello di attuare quella scelta.

+11

+1 - Mi sento sempre come strategia e fabbrica andare di pari passo. –

+0

Grazie per avermi aiutato a capire che cosa è/non è lo schema della strategia. Ho modificato il mio post per mostrare come ho finito per implementarlo. – JSprang

+0

@Brabster A volte trovo difficile cogliere il momento giusto in cui dovresti abbandonare l'istruzione switch e passare (gioco di parole) al pattern di Stategy. Ad esempio, se hai 30 casi di switch molto piccoli e semplici che in caso di andare per il modello di strategia si trasformeranno in ulteriori 30 classi - è una buona ragione per passare o in realtà è meglio lasciare quei piccoli pezzi di codice non-così-pulito ? – mmmm

13

Non penso che il tuo passaggio qui nella tua app demo sia in realtà parte del modello di strategia stesso, è solo utilizzato per esercitare le due diverse strategie che hai definito.

L'avvertenza "interruttori contrassegnati da una bandiera rossa" si riferisce all'interruttore all'interno della strategia; ad esempio, se hai definito una strategia "GenericDrawer" e hai determinato se l'utente desiderava internamente un SquareDrawer o CircleDrawer utilizzando un passaggio rispetto a un valore di parametro, non si otterrebbe il vantaggio del modello di strategia.

15

Quanto segue è una soluzione sovradimensionata al problema solo per evitare le dichiarazioni 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

Si può anche sbarazzarsi di if con l'aiuto di un dizionario

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

Mi piace! Lo sto estendendo per popolare il dizionario Factories dal mio contenitore per le dipendenze, registrando più implementazioni con nome dell'interfaccia factory. – Will

0

Un po 'di ritardo, ma per tutti coloro che sono ancora interessati a rimuovere completamente un'istruzione condizionale.

 class Program 
    { 
     Lazy<Dictionary<Enum, Func<IStrategy>>> dictionary = new Lazy<Dictionary<Enum, Func<IStrategy>>>(
      () => 
       new Dictionary<Enum, Func<IStrategy>>() 
       { 
        { Enum.StrategyA, () => { return new StrategyA(); } }, 
        { Enum.StrategyB, () => { return new StrategyB(); } } 
       } 
      ); 

     IStrategy _strategy; 

     IStrategy Client(Enum enu) 
     { 
      Func<IStrategy> _func 
      if (dictionary.Value.TryGetValue(enu, out _func)) 
      { 
       _strategy = _func.Invoke(); 
      } 

      return _strategy ?? default(IStrategy); 
     } 

     static void Main(string[] args) 
     { 
      Program p = new Program(); 

      var x = p.Client(Enum.StrategyB); 
      x.Create(); 
     } 
    } 

    public enum Enum : int 
    { 
     StrategyA = 1, 
     StrategyB = 2 
    } 

    public interface IStrategy 
    { 
     void Create(); 
    } 
    public class StrategyA : IStrategy 
    { 
     public void Create() 
     { 
      Console.WriteLine("A"); 
     } 
    } 
    public class StrategyB : IStrategy 
    { 
     public void Create() 
     { 
      Console.WriteLine("B"); 
     } 
    } 
Problemi correlati