The notion of code smells is often brought up when talking about refactoring. Code smells are somewhat of a gut-check for your code; if you have a “smelly” block of code, it probably needs some refactoring. So, code smells are essentially bad patterns. My favorite list of code smells can be found here. In reviewing this list, there’s one smell that was surprising to me: switch statements.
The case against switch statements is that they are a violation of the DRY principle. A complex If-ElseIf-ElseIf-etc.-Else statement exhibits the same issue. It’s the same pattern repeated over and over. But it’s so simple, how can it be handled differently? There are two techniques that I recommend: polymorphism and delegate mapping.
Refactor Using Polymorphism
Polymorphism is a perfect solution for many switch statements. In the following sample code, we have a Mammal class with a property that indicates what type of mammal is represented. The SayWhat method uses this property to decide which output to produce.
public class Mammal { public MammalType MType { get; set; } public void SayWhat() { switch (MType) { case MammalType.Human: Console.WriteLine("Hello!"); break; case MammalType.Dog: Console.WriteLine("Bark!"); break; case MammalType.Cat: Console.WriteLine("Meow!"); break; } } }
In order to use polymorphism, we must first refactor the Mammal class. Rather than using a property to represent type, we can create additional types. (Makes sense, doesn’t it?) Making the SayWhat method abstract in our base class will force the new derived classes to provide their own implemtnation.
// refactored! public abstract class Mammal { public abstract void SayWhat(); } public class Human : Mammal { public override void SayWhat() { Console.WriteLine("Hello!"); } } public class Dog : Mammal { public override void SayWhat() { Console.WriteLine("Bark!"); } } public class Cat : Mammal { public override void SayWhat() { Console.WriteLine("Meow!"); } }
Refactor Using Delegate Mapping
Another scenario that I’ve run into is one where the code path is determined by hierarchical conditionals. Creating new objects in order to apply the polymorphic solution may not be appropriate. This is where we can use delegate mapping to solve the problem.
Consider this code:
public class Mammal { public bool IsSleeping { get; set; } public bool IsEating { get; set; } public int Age { get; set; } public bool IsDrunk { get; set; } public void SayWhat() { if (IsSleeping) { Console.WriteLine("Zzz"); } else if (IsEating) { Console.WriteLine("Nom nom nom"); } else if (Age > 21 && IsDrunk) { Console.WriteLine("*hiccup*"); } else if (IsDrunk) { Console.WriteLine("*barf*"); } } }
We can build a delegate map using Predicates and Actions. Here’s what the refactored code might look like
// refactored! public class Mammal { public bool IsSleeping { get; set; } public bool IsEating { get; set; } public int Age { get; set; } public bool IsDrunk { get; set; } public void SayWhat() { var delegateMap = new Dictionary<Predicate<Mammal>, Action> { { x => x.IsSleeping, () => Console.WriteLine("Zzz") }, { x => x.IsEating, () => Console.WriteLine("Nom nom nom") }, { x => x.Age >= 21 && x.IsDrunk, () => Console.WriteLine("*hiccup*") }, { x => x.IsDrunk, () => Console.WriteLine("*barf*") }, }; delegateMap.First(x => x.Key(this)).Value(); } }
A few notes about this specific implementation:
- It uses the LINQ First method to find and execute the first Action
- It will produce an InvalidOperationException if no match is found
- If more than one match is found, only the first delegate will be executed
Thank you sooooooooooooo much man.. It’s an old topic but it blew me away!!!