![]() |
Intermediate
License: The Code Project Open License (CPOL)
Refactoring Switch Statements (Take 2)By emiajHow to refactor Switch statements. |
C#, .NET, All-Topics, Dev
|
||||||||||||
|
Advanced Search Add to IE Search |
|
|
|
||||||||||||||||
Hello to everyone, had been such a long time since my last article. Many interesting things had been going on in Sansir lately that had been taking my entire free time, and also I’m a man with strong family links, so that makes it difficult to publish articles frequently. I am not complaining about it, not at all, color me “happy”.
OK, stop whining.
In one of our previous articles, I showed you how to refactor a piece of code which was deciding what method to execute based on the evaluation
result of a switch statement; you can read about it here.
As a final note, I stated in the article that the presented code could be refactored even further, because if you look carefully, we had just moved the maintenance nightmare to another place in the program, but the original problem still exists.
What to do then? This is what I would do:
public interface INewsDisplayer
{
void Display();
}
public class NewsDisplayerDefinitionAttribute : Attribute
{
public NewsDisplayerDefinitionAttribute(Sports sport)
{
Sport = sport;
}
public Sports Sport { get; private set; }
}
An implementation of this would look like:
[NewsDisplayerDefinition(Sports.Soccer)]
public class SoccerNewsDisplayer : INewsDisplayer
{
#region INewsDisplayer Members
public void Display()
{
Console.WriteLine("Displaying News for Soccer");
// Real implementation below
// Do something
}
#endregion
}
Sport parameter
and returns an IEnumerable<INewsDisplayer> instance. One difference with our previous attempt is that we are going to be able to define
more than one implementation for a given Sport. This factory class will scan all the assemblies for the current AppDomain, and if it finds a type which follows the pattern that
I have described in the previous point, it will read the metadata and append an instance of this type to a dictionary that will serve us
as our lookup table to return the correct instances for the requested Sport. This factory class looks like:public static class NewsDisplayerFactory
{
private static readonly IDictionary<Sports, IEnumerable<INewsDisplayer>>
lookupTable = new Dictionary<Sports, IEnumerable<INewsDisplayer>>();
static NewsDisplayerFactory()
{
BootStrap();
}
private static void BootStrap()
{
// Find all types in all the assemblies from the current appdomain
// that have a correct implementation of
// News Displayer (NewsDisplayerDefinitionAttribute + INewsDisplayer)
var interfaceFullName = typeof (INewsDisplayer).FullName;
var newsDisplayerAttributeType = typeof (NewsDisplayerDefinitionAttribute);
var result = from
assembly in AppDomain.CurrentDomain.GetAssemblies()
from
type in assembly.GetTypes()
let
definition = type
.GetCustomAttributes(newsDisplayerAttributeType, true)
.Cast<NewsDisplayerDefinitionAttribute>()
.FirstOrDefault()
where
type.GetInterface(interfaceFullName) != null && definition != null
group
(INewsDisplayer) Activator.CreateInstance(type)
by definition.Sport;
// Filling the dictionary
foreach (var item in result)
{
lookupTable.Add(item.Key, item.ToList());
}
}
public static IEnumerable<INewsDisplayer> GetInstance(Sports sport)
{
IEnumerable<INewsDisplayer> newsDisplayer;
if (lookupTable.TryGetValue(sport, out newsDisplayer))
{
return newsDisplayer;
}
else
{
throw new NotImplementedException(string.Format(
"The method for the sport {0} is not implemented", sport));
}
}
}
SportNews class to change its context by passing to it the kind of Sport that it is going to handle. That will look like:public class SportNews
{
private IEnumerable<INewsDisplayer> newsDisplayer;
public void SetContext(Sports sport)
{
newsDisplayer = NewsDisplayerFactory.GetInstance(sport);
}
public void DisplayNews()
{
foreach (var displayer in newsDisplayer)
{
displayer.Display();
}
}
}
That’s it, with our new approach, we can continue adding NewsDisplayers and use those in our program without touching other parts
of our program; doing it by adding more classes to our assembly or dropping assemblies into the bin directory (isn’t this called the plug-in model?).
Finally, I’ll like to show our Program class where every component is used to perform the original idea for this program:
class Program
{
private static string options = null;
private static readonly Type sportsEnumType = typeof(Sports);
static void Main(string[] args)
{
var news = new SportNews();
while (true)
{
Console.WriteLine();
DisplayOptions();
var key = Console.ReadKey().KeyChar.ToString();
Console.WriteLine();
try
{
var sport = (Sports)(Enum.Parse(sportsEnumType, key));
news.SetContext(sport);
news.DisplayNews();
}
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();
Sports sport = (Sports)(enumValue);
optionsBuilder.AppendFormat("To display the news for {0} press {1}\n",
sport, enumValue);
}
options = optionsBuilder.ToString();
}
Console.WriteLine(options);
}
}
You can download the working sample from here.
Happy coding, see you soon.
| You must Sign In to use this message board. | |||||||||||||||
|
|||||||||||||||
|
|||||||||||||||
|
|||||||||||||||
|
|||||||||||||||
General
News
Question
Answer
Joke
Rant
Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+PgUp/PgDown to switch pages.
|
PermaLink |
Privacy |
Terms of Use
Last Updated: 2 Jul 2009 Editor: Smitha Vijayan |
Copyright 2009 by emiaj Everything else Copyright © CodeProject, 1999-2010 Web19 | Advertise on the Code Project |