It's
previously been established that I have a deep and abiding distaste for switch statements. This post will discuss alternate mechanisms that display superior maintainability and flexibility.
Let's take a trivial example switch statement:
var action = Console.ReadLine();
switch (action)
{
case "blah":
Console.WriteLine("All a bit blah.");
break;
case "stuff":
Console.WriteLine("Stuff happened.");
break;
case "other stuff":
Console.WriteLine("Other stuff happened.");
break;
default:
Console.WriteLine("Apparently you don't know what you're doing.");
break;
}
We see here four possible actions, which is not that unmaintainable. Let's assume you have a new requirement to add another 50 actions, which will do a variety of tasks other than write to the console. And just for fun the list needs to be modifiable to allow custom plugins which will be dynamically added by end users and be sourced from third parties who may write them months or years after the application ships. A switch statement could be used for the first requirement even though the result would be an unmaintainable abomination. The second requirement cannot be handled by a switch statement as its behaviour is fixed at compile time.
The alternative I will discuss in this post uses delegates to break up the logic. The delegate I'll use for his example is the
System.Action delegate provided in .NET 3.5 but this technique works with any delegate. This gives:
var actions = new Dictionary<string, Action>
{
{ "blah", BlahAction },
{ "stuff", StuffAction },
{ "other stuff", OtherStuffAction },
};
var action = Console.ReadLine();
if (action != null && actions.ContainsKey(action))
{
actions[action]();
}
else
{
DefaultAction();
}
Each of the actions gets a method:
private static void BlahAction()
{
Console.WriteLine("All a bit blah.");
}
private static void StuffAction()
{
Console.WriteLine("Stuff happened.");
}
private static void OtherStuffAction()
{
Console.WriteLine("Other stuff happened.");
}
private static void DefaultAction()
{
Console.WriteLine("Apparently you don't know what you're doing.");
}
This may initially seem more work. However, there are a number of factors to consider:
- Each method may now be more easily unit tested in isolation.
- The dictionary may be dynamically created and modified. This allows the action list to be determined at runtime, providing considerable flexibility.
- The example above contains only trivial logic. As each action grows in complexity the switch statement becomes larger. If you are keeping the switch statement methods would need to be extracted anyway to manage the size of the switch. This approach simply goes one step further in how these methods are invoked.
- The methods so extracted may be used in multiple locations. Logic tied up in a switch statement generally has poor reusability.
- It is possible to query the list of actions at runtime to see if actions are supported. This is not (easily) possible with a switch.
- If the selection logic changes to something other than a simple key lookup this method already has the action logic factored out. The change to how the logic to be used is invoked will therefore be significantly less intrusive.
- This method does not require that all actions be implemented within a single class. Actions may be distributed to the most appropriate locations in the application and hooked up as needed.