52

I've been doing some reading on the Strategy Pattern, and have a question. I have implemented a very basic Console Application below to explain what I'm asking.

I have read that having 'switch' statements is a red flag when implementing the strategy pattern. However, I can't seem to get away from having a switch statement in this example. Am I missing something? I was able to remove the logic from the Pencil, but my Main has a switch statement in it now. I understand that I could easily create a new TriangleDrawer class, and wouldn't have to open the Pencil class, which is good. However, I would need to open Main so that it would know which type of IDrawer to pass to the Pencil. Is this just what needs to be done if I'm relying on the user for input? If there's a way to do this without the switch statement, I'd love to see it!

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

Implemented Solution shown below (Thanks to all who responded!) This solution got me to the point where the only thing I need to do to use a new IDraw object is to create it.

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();
            }
        }
    }
JSprang
  • 12,481
  • 7
  • 30
  • 32
  • 1
    Switch statement that can cause open/closed principle to be violated later is bad. Strategy pattern helps separating the switch statement away from the place you want to keep it closed, but you still have to deal with choosing strategy/implementation some where either it is switch statement, if/else/if, or using LINQ Where (which is my favorite :-) By the way, strategy pattern also helps unit testing by allowing you to mock strategy implementation easily too. – kimsk Jan 23 '13 at 06:35

5 Answers5

64

Strategy isn't a magic anti-switch solution. What it does do is give modularise your code so that instead of a big switch and business logic all mixed up in a maintenance nightmare

  • your business logic is isolated and open for extension
  • you have options as for how you create your concrete classes (see Factory patterns for example)
  • your infrastructure code (your main) can be very clean, free of both

For example - if you took the switch in your main method and created a class which accepted the command line argument and returned an instance of IDraw (i.e. it encapsulates that switch) your main is clean again and your switch is in a class whose sole purpose is to implement that choice.

Ali Dehghani
  • 46,221
  • 15
  • 164
  • 151
brabster
  • 42,504
  • 27
  • 146
  • 186
  • 19
    +1 - I always feel like Strategy and Factory go hand in hand. – NG. Sep 30 '10 at 19:44
  • Thanks for helping me understand what the Strategy Pattern is/isn't. I have edited my post to show how I ended up implementing this. – JSprang Oct 01 '10 at 20:46
  • 1
    @Brabster sometimes I find it difficult to catch proper moment when You should abandon switch statement and switch ( pun intended ) to Stategy pattern. For example, if you have 30 very little and simple switch-cases that in case of going for Strategy pattern will turn into additional 30 classes - is it good reason to switch or actually better to leave those little pieces of not-so-clean code? – mmmm Nov 21 '16 at 19:33
  • 1
    @mmmm I tend to wait until it hurts before changing from switch to strategy. If you find yourself constantly having to make a change in two places and you keep forgetting/it's not convenient, either for you or people you're working with then taking a bit of time to refactor to strategy may be worth it. – brabster Nov 22 '16 at 11:10
16

I don't think your switch here in your demo app is actually part of the strategy pattern itself, it is just being used to exercise the two different strategies you have defined.

The "switches being a red flag" warning refers to having switches inside the strategy; for example, if you defined a strategy "GenericDrawer", and had it determine if the user wanted a SquareDrawer or CircleDrawer internally using a switch against a parameter value, you would not be getting the benefit of the strategy pattern.

Guy Starbuck
  • 21,603
  • 7
  • 53
  • 64
16

The following is an over engineered solution to your problem solely for the sake of avoiding if/switch statements.

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");
}
RJFalconer
  • 10,890
  • 5
  • 51
  • 66
Muhammad Hasan Khan
  • 34,648
  • 16
  • 88
  • 131
14

You can also get rid of if with help of a dictionary

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();
RJFalconer
  • 10,890
  • 5
  • 51
  • 66
Muhammad Hasan Khan
  • 34,648
  • 16
  • 88
  • 131
  • I like this! I'm extending it to populate the Factories dictionary from my dependency injection container, registering multiple named implementations of the factory interface. – Will Apr 03 '17 at 17:58
5

A little to late but for anyone that still is interested in fully removing a conditional statement.

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