web-dev-qa-db-fra.com

Modèle de stratégie sans déclaration de «changement»?

J'ai fait de la lecture sur le modèle de stratégie et j'ai une question. J'ai implémenté une application console très basique ci-dessous pour expliquer ce que je demande.

J'ai lu que le fait d'avoir des instructions "switch" est un drapeau rouge lors de la mise en œuvre du modèle de stratégie. Cependant, je ne parviens pas à éviter d'avoir une instruction switch dans cet exemple. Suis-je en train de manquer quelque chose? J'ai pu supprimer la logique du Pencil, mais mon Main contient maintenant une instruction switch. Je comprends que je pourrais facilement créer une nouvelle classe TriangleDrawer, et je n'aurais pas à ouvrir la classe Pencil, ce qui est bien. Cependant, j'aurais besoin d'ouvrir Main pour qu'il sache quel type de IDrawer pour passer au Pencil. Est-ce juste ce qui doit être fait si je compte sur l'utilisateur pour la saisie? S'il y a un moyen de le faire sans l'instruction switch, j'adorerais le voir!

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

Solution implémentée illustrée ci-dessous (Merci à tous ceux qui ont répondu!) Cette solution m'a amené au point où la seule chose que je dois faire pour utiliser un nouvel objet IDraw est de créer il.

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

La stratégie n'est pas une solution anti-interrupteur magique. Ce qu'il fait, c'est de modulariser votre code de sorte qu'au lieu d'un gros commutateur et d'une logique métier tous mélangés dans un cauchemar de maintenance

  • votre logique métier est isolée et ouverte à l'extension
  • vous avez des options quant à la façon dont vous créez vos classes concrètes (voir les modèles d'usine par exemple)
  • votre code d'infrastructure (votre principal) peut être très propre, sans les deux

Par exemple - si vous avez pris le commutateur dans votre méthode principale et créé une classe qui a accepté l'argument de ligne de commande et renvoyé une instance d'IDraw (c'est-à-dire qu'il encapsule ce commutateur), votre main est à nouveau propre et votre commutateur est dans une classe dont le seul but est de mettre en œuvre ce choix.

53
brabster

Ce qui suit est une solution trop technique à votre problème uniquement pour éviter les instructions 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");
}
17
Muhammad Hasan Khan

Je ne pense pas que votre commutateur ici dans votre application de démonstration fasse en fait partie du modèle de stratégie lui-même, il est simplement utilisé pour exercer les deux stratégies différentes que vous avez définies.

L'avertissement "les commutateurs étant un drapeau rouge" fait référence à des commutateurs à l'intérieur la stratégie; par exemple, si vous définissiez une stratégie "GenericDrawer" et que vous décidiez si l'utilisateur voulait un SquareDrawer ou CircleDrawer en interne en utilisant un commutateur par rapport à une valeur de paramètre, vous ne bénéficieriez pas du modèle de stratégie.

14
Guy Starbuck

Vous pouvez également vous débarrasser de if à l'aide d'un dictionnaire

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();
14
Muhammad Hasan Khan

Un peu trop tard, mais pour quiconque souhaite toujours supprimer complètement une déclaration conditionnelle.

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

Dans le cas où vous n'avez pas besoin de polymorphisme. L'exemple utilise Action mais tout autre type de délégué peut être transmis. Func si vous souhaitez renvoyer quelque chose

1
MCR