Before going into details about how you could refactor a switch statement first I should say that there's nothing wrong with this statement (as with any other C#, VB.NET or whatever statement). Most probably you would want to refactor a switch in one of these scenarios:
One possible way to refactor a switch statement is by using the Derick Bailey's approach and use the "Descriptive State Enumeration" approach. What Derick manages to do is turn this code:
bool
shouldShowOrder;
switch
(order.Status)
{
case
OrderStatus.Totaled:
{
shouldShowOrder =
true
;
break
;
}
case
OrderStatus.Tendered:
{
shouldShowOrder =
true
;
break
;
}
default
:
{
shouldShowOrder =
false
;
}
}
if
(shouldShowOrder)
ShowTheOrderForFulfillment(order);
into something like this:
public
abstract
class
OrderStatus
{
public
static
OrderStatus InProcess =
new
InProcessStatus();
public
static
OrderStatus Totaled =
new
TotaledStatus();
public
static
OrderStatus Tendered =
new
TenderedStatus();
public
abstract
bool
DisplayForFullfillment
{
get
;
}
private
OrderStatus()
{
}
}
public
class
InProcessStatus : OrderStatus
{
public
bool
DisplayForFullfillment
{
get
{
return
false
;
}
}
}
public
class
TotaledStatus : OrderStatus
{
public
bool
DisplayForFullfillment
{
get
{
return
true
;
}
}
}
public
class
TenderedStatus : OrderStatus
{
public
bool
DisplayForFullfillment
{
get
{
return
true
;
}
}
}
public
class
Order
{
public
OrderStatus Status
{
get
;
set
;
}
}
double
total = order.GetTotal();
order.Status = OrderStatus.Totaled;
In short what we do here (actually what Derick suggests) is to wrap each case into a new state and delegate some responsibility to each state rather than doing everything in the switch statement in strongly procedural code. This way when you need to introduce a new behavior you won't need to modify the existing code but rather create a new state.
If for some reason you don't want to delegate responsibility to each state and if it turns out that you'll need too many states you could consider using Chris Brandsma's approach and use a Dictionary. What Chris does is turning this code:
public
string
ReturnSomething(
string
name,
string
value)
{
switch
(name)
{
case
"Chris"
:
return
_valueProcessor.Chris(value);
case
"David"
:
return
_valueProcessor.David(value);
case
"Scott"
:
return
_valueProcessor.Scott(value);
case
"Jason"
:
return
_valueProcessor.Jason(value);
case
"Tony"
:
return
_valueProcessor.Tony(value);
default
:
return
string
.Empty;
}
}
into:
public
Dictionary<
string
, Func<
string
,
string
>> CreateDictionary()
{
var dictionary =
new
Dictionary<
string
, Func<
string
,
string
>>
{
{
"Chris"
, _valueProcessor.Chris},
{
"David"
, _valueProcessor.David},
{
"Jason"
, _valueProcessor.Jason},
{
"Scott"
, _valueProcessor.Scott},
{
"Tony"
, _valueProcessor.Tony}
};
return
dictionary;
}
public
string
ReturnSomething(
string
name,
string
value)
{
if
(_dictionary.ContainsKey(name))
return
_dictionary[name].Invoke(value);
return
string
.Empty;
}
Some of you may not like this kind of refactoring but nevertheless if it helps you have cleaner code at the end, why not?
In some cases you may have a sequence of several operations where each operation relies on the result or the completion of the previous operation. In such scenarios you could take advantage of what Joel Ross suggests, so assuming you have the following lines of code:
public
void
DoColorWork(ColorObject color, ColorManager manager)
{
switch
(color.PrimaryColor)
{
case
PrimaryColor.Red:
manager.OperationOneRed(color);
break
;
case
PrimaryColor.Green:
manager.OperationOneGreen(color);
break
;
case
PrimaryColor.Blue:
manager.OperationOneBlue(color);
break
;
}
manager.OperationTwo(color);
switch
(color.PrimaryColor)
{
case
PrimaryColor.Red:
manager.OperationThreeRed(color);
break
;
case
PrimaryColor.Green:
manager.OperationThreeGreen(color);
break
;
case
PrimaryColor.Blue:
manager.OperationThreeBlue(color);
break
;
}
}
using the Strategy pattern you could refactor them to:
public
void
DoColorWork(ColorObject color)
{
ColorManager manager = GetColorManager(color);
manager.OperationOne(color);
manager.OperationTwo(color);
manager.OperationThree(color);
}
public
ColorManager GetColorManager(ColorObject color)
{
switch
(color.PrimaryColor)
{
case
PrimaryColor.Red:
return
new
RedColorManager();
case
PrimaryColor.Green:
return
new
GreenColorManager();
case
PrimaryColor.Blue:
return
new
BlueColorManager();
}
}
Instead of the Strategy pattern you could try using the Visitor pattern which is also a good option when you feel tempted to implicitly switch by a type's name (example taken from here).
e.g.
public
void
SortEmployee(Employee employee)
{
string
employeeType =
typeof
(Employee).Name;
switch
(employeeType)
{
case
"Engineer"
:
// Cast employee to Engineer and do something
break
;
case
"Salesman"
:
// Cast employee to Salesman and do something
break
;
case
"Manager"
:
// Cast employee to Manager and do something
break
;
}
}
Well, the refactored code will result in much more lines of code but at least we'll be safe if we decide to rename some of the classes inheriting from the Employee class. Also we won't need to cast the employee instance to the different subclasses. And last but not least we'll have code which will have more and clearer points of extensibility:
public
interface
IVisitor
{
void
VisitEngineer(Engineer engineer);
void
VisitSalesman(Salesman salesman);
void
VisitManager(Manager manager);
}
public
abstract
class
Employee
{
public
abstract
void
Accept(IVisitor visitor);
}
public
class
Engineer : Employee
{
public
override
void
Accept(IVisitor visitor)
{
visitor.VisitEngineer(
this
);
}
}
public
class
Salesman : Employee
{
public
override
void
Accept(IVisitor visitor)
{
visitor.VisitSalesman(
this
);
}
}
public
class
Manager : Employee
{
public
override
void
Accept(IVisitor visitor)
{
visitor.VisitManager(
this
);
}
}
public
class
EmployeeSortVisitor : IVisitor
{
public
void
VisitEngineer(Engineer engineer)
{
}
public
void
VisitSalesman(Salesman salesman)
{
}
public
void
VisitManager(Manager manager)
{
}
}
What we should finally do is just instantiate a new EmployeeSortVisitor in the SortEmployee method and call the Accept method of the employee.
e.g.
public
void
SortEmployee(Employee employee)
{
var employeeVisitor =
new
EmployeeSortVisitor();
employee.Accept(employeeVisitor);
}
For those of you who find the switch statement syntax kind of limiting you could check Bart De Smet's functional switch approach.
You'll be able to do some pretty cool things, such as:
new
Switch<
string
>(name)
.Case(s => s.StartsWith(
"B"
), s =>
{
Console.WriteLine(s +
" starts with B."
);
},
true
)
.Case(s => s.StartsWith(
"Ba"
), s =>
{
Console.WriteLine(s +
" starts with Ba."
);
})
.Default(s =>
{
Console.WriteLine(s +
" starts with who knows what."
);
});
Hope this helps :)