Domanda

Ho letto un po' sul modello strategico e ho una domanda.Di seguito ho implementato un'applicazione console molto semplice per spiegare ciò che sto chiedendo.

Ho letto che avere istruzioni di "cambio" è un segnale di allarme quando si implementa il modello di strategia.Tuttavia, non riesco a evitare di avere un'istruzione switch in questo esempio.Mi sto perdendo qualcosa?Sono stato in grado di rimuovere la logica dal file Matita, ma il mio Principale ora contiene un'istruzione switch.Capisco che potrei facilmente crearne uno nuovo TriangoloCassetto classe e non sarebbe necessario aprire il file Matita classe, il che è positivo.Avrei però bisogno di aprire Principale in modo che possa sapere di quale tipo IDrawer passare al Matita.È proprio 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 mostrata di seguito (Grazie a tutti coloro che hanno risposto!) Questa soluzione mi ha portato al punto in cui l'unica cosa che devo fare per usare un nuovo Disegno l'obiettivo è 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();
            }
        }
    }
È stato utile?

Soluzione

La strategia non è una magica soluzione anti-switch.Ciò che fa è modularizzare il codice in modo che invece di un grande cambiamento e una logica di business si confondano in un incubo di manutenzione

  • la tua logica aziendale è isolata e aperta all'estensione
  • hai opzioni su come creare le tue classi concrete (vedi modelli di fabbrica per esempio)
  • il codice della tua infrastruttura (il tuo principale) può essere molto pulito, privo di entrambi

Ad esempio, se hai preso l'opzione nel tuo metodo principale e hai creato una classe che accettava l'argomento della riga di comando e restituiva un'istanza di IDraw (cioèincapsula quell'interruttore) il tuo main è di nuovo pulito e il tuo switch è in una classe il cui unico scopo è implementare quella scelta.

Altri suggerimenti

Quella che segue è una soluzione eccessivamente ingegnerizzata al tuo problema esclusivamente per il gusto di evitarlo if/switch dichiarazioni.

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

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

L'avviso "gli interruttori sono una bandiera rossa" si riferisce alla presenza di interruttori dentro la strategia;ad esempio, se definissi una strategia "GenericDrawer" e determinassi se l'utente desidera uno SquareDrawer o un CircleDrawer internamente utilizzando un interruttore rispetto al valore di un parametro, non otterresti il ​​vantaggio del modello di strategia.

Puoi anche sbarazzartene 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();

Un po' troppo tardi, ma per chiunque sia ancora interessato a rimuovere completamente una dichiarazione 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");
        }
    }
IReadOnlyDictionaru<SomeEnum, Action<T1,T2,T3,T3,T5,T6,T7>> _actions 
{
    get => new Dictionary<SomeEnum, Action<T1,T2,T3,T3,T5,T6,T7>> 
        {
           {SomeEnum.Do, OptionIsDo},
           {SomeEnum.NoDo, OptionIsNoDo}
        } 
}

public void DoSomething(SomeEnum option)
{
    _action[option](1,"a", null, DateTime.Now(), 0.5m, null, 'a'); // _action[option].Invoke(1,"a", null, DateTime.Now(), 0.5m, null, 'a');
}


pub void OptionIsDo(int a, string b, object c, DateTime d, decimal e, object f, char c)
{
   return ;
}

pub void OptionIsNoDo(int a, string b, object c, DateTime d, decimal e, object f, char c)
{
   return ;
}

Nel caso in cui non sia necessario il polimorfismo.L'esempio usa Action ma è possibile passare qualsiasi altro tipo di delegato.Func se vuoi restituire qualcosa

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top