43

Я читал о шаблоне стратегии и задал вопрос. Я объяснил, что я прошу, для этого я использовал базовое консольное приложение.Шаблон стратегии без инструкций 'switch'?

Я читал, что при выполнении стратегического шаблона наличие операторов «switch» является красным. Тем не менее, я не могу уклониться от предложения switch в этом примере. Я что-то упускаю? Я смог удалить логику с Карандаш, но у моего Main есть инструкция switch в нем сейчас. Я понимаю, что я мог бы легко создать новый класс TriangleDrawer, и не нужно было бы открывать класс Pencil, что хорошо. Однако мне нужно было бы открыть Main, чтобы он знал, какой тип IDrawer перейдет на Карандаш. Это то, что нужно сделать, если я полагаюсь на пользователя для ввода? Если есть способ сделать это без оператора 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(); 
      } 
     } 
    } 
+1

Заявление о переключении, которое может привести к нарушению открытого/закрытого принципа в дальнейшем, является плохим. Шаблон стратегии помогает отделить оператор switch от места, где вы хотите его закрыть, но вам все же приходится иметь дело с выбором стратегии/реализации, где либо это оператор switch, либо/else/if, либо с помощью LINQ Where (который мой любимый :-) Кстати, стратегический паттерн также помогает модульное тестирование, позволяя вам также легко имитировать реализацию стратегии. – kimsk

ответ

46

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

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

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

+11

+1 - Я всегда чувствую, что Strategy and Factory идут рука об руку. –

+0

Спасибо, что помогли мне понять, что представляет собой шаблон стратегии. Я редактировал свой пост, чтобы показать, как я это сделал. – JSprang

+0

@Brabster иногда мне сложно поймать подходящий момент, когда вам следует отказаться от оператора switch и переключаться (каламбур) на шаблон Stategy. Например, если у вас есть 30 очень маленьких и простых коммутационных шкафов, которые в случае перехода на шаблон стратегии превратятся в дополнительные 30 классов - это хорошая причина переключиться или на самом деле лучше оставить эти маленькие кусочки не очень чистого кода ? – mmmm

13

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

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

15

Ниже приведено более детальное решение вашей проблемы исключительно для того, чтобы избежать сообщений 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

Вы можете также избавиться от 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(); 
+0

Мне это нравится! Я расширяю его, чтобы заполнить словарь Factories из моего контейнера инъекций зависимостей, зарегистрировав несколько именованных реализаций фабричного интерфейса. – Will

0

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

 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"); 
     } 
    } 
Смежные вопросы