Refactoring Switches to Classes
- Switch is Just a Fancy If Else
- Pulling out the Switch: It’s Time for a Whooping
- Refactoring Switches Advanced
I’ve even created a defaultable dictionary for refactoring a switch statement into a dictionary of actions.
This time, I am going to talk about refactoring switches when you have switch statements operating on the same set of data, but have different actions in different circumstances.
First let’s recap
When I talked about refactoring switches before, we were mainly dealing with a single switch statement somewhere in code.
In the case where you have only a single switch statement, or multiple switch statements that do the same thing based on the data, using a dictionary is still a great way to go.
However, there are going to be circumstances where you are going to be switching on the same data, but in different contexts. In these cases, you will want to perform different actions.
Let’s look at an example.
// In fighting code
switch(classType)
{
case WARRIOR:
swingSword();
break;
case MAGE:
castSpell();
break;
case THIEF:
backstab():
break;
}
// In wear armor code
switch(classType)
{
case WARRIOR:
return CAN_WEAR;
case MAGE:
return isConsideredLightArmor(armor);
case THIEF:
if(isSneaking)
return NOT_NOW;
return isConsideredLightArmor(armor);
}
In this example, we are switching on the same enumeration, but we are doing it in different locations of the code.
Using a dictionary would not work well here because we would need multiple dictionaries.
We still don’t want to leave this as it is though, because the code is pretty messy and fragile.
Separation of concerns
The problem is the code that contains these switch statements has too much responsibility. It is being asked to handle logic for each one of our character class types.
What we need to do to improve this code is refactor the enumerations into their own classes. Each switch statement will become a method that will be implemented by our enumeration based class.
If we are using Java, we can use Java’s enumeration implementation that allows for methods on an enumeration. If we are using a language like C#, we still have to map the enumeration value to each class.
Let’s start by making our classes.
First we need a base class, or interface.
public interface CharacterClass
{
void Attack();
ArmorResponse WearArmor(armor);
}
Now we can create classes that implement this interface, that contain the logic that was in each switch statement.
public class Warrior : CharacterClass
{
void Attack()
{
swingSword();
}
ArmorResponse WearArmor(armor)
{
return CAN_WEAR;
}
}
public class Mage : CharacterClass
{
void Attack()
{
castSpell();
}
ArmorResponse WearArmor(armor)
{
return isConsideredLightArmor(armor);
}
}
public class Thief : CharacterClass
{
void Attack()
{
backstab();
}
ArmorResponse WearArmor(armor)
{
if(isSneaking)
return NOT_NOW;
return isConsideredLightArmor(armor);
}
}
Next we can map our enumeration to our class.
public Dictionary characterDictionary =
new Dictionary {
{ WARRIOR, new Warrior() },
{ MAGE, new Mage() },
{ THIEF, new Thief() }
};
We could also get rid of the enumeration if we wanted, and just create the appropriate class. It will depend on what your existing code looks like.
No more switches!
Now let’s take a look at what we end up with in the two locations where we had switches.
// In fighting code
myCharacter.Attack();
// In wear armor code
var armorResponse = myCharacter.WearArmor(armor);
If we want to add a new character class type, we just add a new class that implements the CharacterClass interface and put a mapping in our dictionary, or in our character initialization code.
If we end up having other places in our logic where different character class types should have different behavior, we just add a method to our CharacterClass interface and implement it in any classes that implement CharacterClass.
Our code is much more maintainable, and easier to understand.
References: Refactoring Switches to Classes from our NCG partner John Sonmez at the Making the Complex Simple blog.