Wednesday, January 14, 2009

Refactoring "switch cases".

Hello to everyone, I want to show you how to apply a simple refactoring trick to a common pattern, the "switch case" statement.

Please take a look at the following program (a console app) :

class Program

    {

        private static string options = null;

        private static readonly Type sportsEnumType = typeof(SportNews.Sports);

        static void Main(string[] args)

        {

            SportNews news = new SportNews();

            while (true)

            {

                Console.WriteLine();

                DisplayOptions();

                string key = Console.ReadKey().KeyChar.ToString();

                Console.WriteLine();

                try

                {

                    SportNews.Sports sport = (SportNews.Sports)(Enum.Parse(sportsEnumType, key));

                    news.DisplayNews(sport);

                }

                catch(Exception ex)

                {

                    Console.WriteLine(ex);

                    Console.ReadLine();

                    return;

                }

 

            }

        }

        private static void DisplayOptions()

        {

            if (options == null)

            {

                StringBuilder optionsBuilder = new StringBuilder();

                FieldInfo[] enumFields = sportsEnumType.UnderlyingSystemType.GetFields(BindingFlags.Public | BindingFlags.Static);

                foreach (FieldInfo enumField in enumFields)

                {

                    object enumValue = enumField.GetRawConstantValue();

                    SportNews.Sports sport = (SportNews.Sports)(enumValue);

                    optionsBuilder.AppendFormat("To display the news for {0} press {1}\n", sport, enumValue);

                }

                options = optionsBuilder.ToString();

            }

            Console.WriteLine(options);

        }

    }

 

    public class SportNews

    {

        public enum Sports

        {

            Soccer = 0,

            BasketBall = 1,

            Rugby = 2,

            VolleyBall = 3

        }

        public void DisplayNews(Sports sport)

        {

            switch (sport)

            {

                case Sports.Soccer:

                    DisplayNewsForSoccer();

                    break;

                case Sports.BasketBall:

                    DisplayNewsForBasketBall();

                    break;

                case Sports.Rugby:

                    DisplayNewsForRugby();

                    break;

                case Sports.VolleyBall:

                    DisplayNewsForVolleyBall();

                    break;

                default:

                    throw new NotImplementedException(string.Format("The method for the sport {0} is not implemented", sport));

            }

        }

 

        private void DisplayNewsForSoccer()

        {

            Console.WriteLine("Displaying News for Soccer");

            // Real implementation below

            // Do something

        }

        private void DisplayNewsForBasketBall()

        {

            Console.WriteLine("Displaying News for BasketBall");

            // Real implementation below

            // Do something

        }

 

        private void DisplayNewsForRugby()

        {

            Console.WriteLine("Displaying News for Rugby");

            // Real implementation below

            // Do something

        }

 

        private void DisplayNewsForVolleyBall()

        {

            Console.WriteLine("Displaying News for VolleyBall");

            // Real implementation below

            // Do something

        }

 

    }

What this does?, well, simply ask to the user to press a given key to execute a determined action.

The running program looks like this:

console_app_running

Very simple isn't?

This is fine for the number of alternatives that we are going to handle, but,what about if you add more items (+10) to the enum, you would need to add more cases to the switch statement, and so on. This is a very error prone process and also a maintenance nightmare.

What to do then? Well, the solution is very simple, you would need to change the switch statement by a lookup table, a Dictionary object is a good candidate to do this.

SHOW ME THE CODE!!. Here it is:

First lets create our Dictionary instance in the SportNews class constructor :

        private Dictionary<Sports, Action> lookupTable;

        public SportNews()

        {

            lookupTable = new Dictionary<Sports, Action>()

            {

                { Sports.Soccer, DisplayNewsForSoccer },

                { Sports.BasketBall, DisplayNewsForBasketBall },

                { Sports.Rugby, DisplayNewsForRugby },

                { Sports.VolleyBall, DisplayNewsForVolleyBall }

            };

        }

Now lets change the DiplayNews method to get ride of the switch statement, it will end up as:

        public void DisplayNews(Sports sport)

        {

            Action displayMethod;

            if (lookupTable.TryGetValue(sport, out displayMethod))

            {

                displayMethod.Invoke();

            }

            else

            {

                throw new NotImplementedException(string.Format("The method for the sport {0} is not implemented", sport));

            }

        }

Isn't this more simple and clean?

Obviously, this code could be refactored even more, but for the purpose of the article I think is fine.

The final working demo can be found here.

Bye bye.

Etiquetas de Technorati: ,,

1 comment:

Anonymous said...

where did this came from "displayMethod" ?

if trying the code you suggested, will have a displayMethod = null hence will cause an error.