Шаблон стратегии без заявлений «переключателя»? - PullRequest
48 голосов
/ 30 сентября 2010

Я читал шаблон стратегии и у меня есть вопрос. Я реализовал очень простое консольное приложение ниже, чтобы объяснить, о чем я спрашиваю.

Я прочитал, что наличие операторов switch является красным флагом при реализации шаблона стратегии. Тем не менее, я не могу избежать использования оператора switch в этом примере. Я что-то пропустил? Мне удалось удалить логику из Pencil , но мой Main теперь содержит оператор switch. Я понимаю, что мог бы легко создать новый класс TriangleDrawer , и мне не пришлось бы открывать класс Pencil , что хорошо. Однако мне нужно открыть Main , чтобы он знал, какой тип IDrawer передать на Pencil . Это только то, что нужно сделать, если я полагаюсь на пользователя для ввода? Если есть способ сделать это без оператора switch, я бы хотел это увидеть!

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

Реализованное решение показано ниже (Спасибо всем, кто откликнулся!) Это решение привело меня к тому, что единственное, что мне нужно сделать, чтобы использовать новый объект IDraw , - это создать его.

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

Ответы [ 6 ]

53 голосов
/ 30 сентября 2010

Стратегия не является волшебным решением против переключения. Что он делает, так это дает модульную структуру вашего кода, так что вместо большого коммутатора и бизнес-логики все смешалось в кошмаре обслуживания

  • ваша бизнес-логика изолирована и открыта для расширения
  • у вас есть варианты создания конкретных классов (см., Например, шаблоны фабрики)
  • код вашей инфраструктуры (ваш основной) может быть очень чистым, без обоих

Например - если вы взяли переключатель в своем методе main и создали класс, который принял аргумент командной строки и возвратил экземпляр IDraw (то есть он инкапсулирует этот переключатель), ваш main снова чист, и ваш переключатель находится в классе чья единственная цель состоит в том, чтобы осуществить этот выбор.

17 голосов
/ 30 сентября 2010

Ниже приведено более сложное решение вашей проблемы исключительно для того, чтобы избежать 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");
}
14 голосов
/ 30 сентября 2010

Вы также можете избавиться от if с помощью словаря

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 голосов
/ 30 сентября 2010

Я не думаю, что ваш переключатель здесь, в вашем демонстрационном приложении, на самом деле является частью самого шаблона стратегии, он просто используется для реализации двух разных стратегий, которые вы определили.красный флажок "предупреждение" означает наличие переключателей внутри стратегии;Например, если вы определили стратегию «GenericDrawer» и попросили ее определить, хочет ли пользователь использовать SquareDrawer или CircleDrawer для внутреннего использования переключателя на значение параметра, вы не получите преимущества шаблона стратегии.

4 голосов
/ 29 января 2018

Немного поздно, но для тех, кто все еще заинтересован в полном удалении условного выражения.

     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");
        }
    }
1 голос
/ 07 марта 2019
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 ;
}

На случай, если вам не нужен полиморфизм.В примере используется Action, но любой другой тип делегата может быть передан. Func, если вы хотите что-то вернуть

...