【问题标题】:Strategy Pattern with no 'switch' statements?没有“switch”语句的策略模式?
【发布时间】:2010-09-30 19:25:00
【问题描述】:

我一直在阅读策略模式,并且有一个问题。我在下面实现了一个非常基本的控制台应用程序来解释我在问什么。

我已经读过,在实施策略模式时,使用“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();
            }
        }
    }

【问题讨论】:

  • switch 语句会导致后面的开/关原则被违反是不好的。策略模式有助于将 switch 语句与您希望保持关闭的位置分开,但您仍然必须处理选择策略/实现的一些地方,它是 switch 语句、if/else/if 或使用 LINQ Where(即我最喜欢的 :-) 顺便说一句,策略模式还允许您轻松地模拟策略实现,从而帮助单元测试。

标签: c# design-patterns dependency-injection strategy-pattern coding-style


【解决方案1】:

策略并不是一种神奇的反转换解决方案。它所做的就是将您的代码模块化,这样就不会将大开关和业务逻辑全部混入维护的噩梦中

  • 您的业务逻辑已隔离并开放以供扩展
  • 您可以选择如何创建具体类(例如,请参阅工厂模式)
  • 您的基础架构代码(您的主代码)可以非常干净,没有两者

例如 - 如果你在你的 main 方法中使用了 switch 并创建了一个接受命令行参数并返回一个 IDraw 实例的类(即它封装了那个 switch)你的 main 又是干净的并且你的 switch 在一个类中其唯一目的是实现该选择。

【讨论】:

  • +1 - 我总觉得战略和工厂齐头并进。
  • 感谢您帮助我了解策略模式是/不是什么。我已经编辑了我的帖子,以展示我最终是如何实现这一点的。
  • @Brabster 有时我发现当你应该放弃 switch 语句并切换(双关语)到 Stategy 模式时,我很难抓住合适的时机。例如,如果您有 30 个非常少且简单的 switch-case,如果采用 Strategy 模式将变成额外的 30 个类 - 是切换的好理由还是留下那些不那么干净的小代码? ?
  • @mmmm 我倾向于等到痛了再从切换到策略。如果您发现自己经常不得不在两个地方进行更改并且您一直忘记/不方便,无论是对您还是与您一起工作的人,那么花一点时间重构策略可能是值得的。
【解决方案2】:

为了避免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");
}

【讨论】:

    【解决方案3】:

    我不认为您在演示应用中的切换实际上是策略模式本身的一部分,它只是用于练习您定义的两种不同策略。

    “开关是一个危险信号”警告是指在策略内部有开关;例如,如果您定义了一个策略“GenericDrawer”,并让它通过一个参数值的开关在内部确定用户是想要 SquareDrawer 还是 CircleDrawer,那么您将无法从策略模式中受益。

    【讨论】:

      【解决方案4】:

      您还可以借助字典摆脱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();
      

      【讨论】:

      • 我喜欢这个!我正在扩展它以从我的依赖注入容器中填充工厂字典,注册工厂接口的多个命名实现。
      【解决方案5】:

      有点晚了,但对于仍然有兴趣完全删除条件语句的任何人。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多