Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refactor long switch statement

I'm program in c# which you controlling by dictating command so now i have a long switch statement. Something like

switch (command)

{
    case "Show commands":
        ProgramCommans.ShowAllCommands();
        break;
    case "Close window":
        ControlCommands.CloseWindow();
        break;
    case "Switch window":
        ControlCommands.SwitchWindow();
        break;
}

and so on

Almost all cases call only one method, methods are not in one class they are distributed in many classes. So the question is, how i could refactor this switch to more elegant way?

like image 240
user2412672 Avatar asked Dec 14 '13 17:12

user2412672


Video Answer


2 Answers

I much prefer the Strategy Pattern for extending switch case statements. First, I create an interface that defines what each rule should look like:

public interface IWindowRule 
{
    string Command { get; }
    void Invoke();
}

Then create a class that implements the interface for each possible case:

public class ShowAllWindowRule : IWindowRule
{
    public string Command => "Show commands";
    private ProgramCommands _progCommands;

    public ShowAllWindowRule(ProgramCommands programCommands) =>
          _progCommands = programCommands;

    public void Invoke() => _progCommands.ShowAllCommands();
}

public class CloseWindowRule : IWindowRule
{
    private ControlCommands _ctrlCommands;
    public string Command => "Close window";

    public CloseWindowRule(ControlCommands ctrlCommands) =>
        _ctrlCommands = ctrlCommands;

    public void Invoke() =>
        _ctrlCommands.CloseWindow();
}

public class SwitchWindowRule : IWindowRule
{
    private ControlCommands _ctrlCommands;
    public string Command => "Switch window";

    public SwitchWindowRule(ControlCommands ctrlCommands) =>
        _ctrlCommands = ctrlCommands;

    public void Invoke() =>
        _ctrlCommands.SwitchWindow();
}

Then your switch statement turns into this:

public void RunWindowRule(IList<IWindowRule> rules, string command)
{
    foreach (IWindowRule rule in rules)
    {
        if (rule.Command == command) rule.Invoke();
    }
}

Now you can pass the function any set of rules you wish and run them making the function adhere to the Open/Closed principle.

I realize this may appear to be a bit of over engineering, and I do think there are more functional solutions that require a bit less work, however this has the added benefit of allowing you to extend this function by creating classes that inject the list of rules for a myriad of circumstances or even make a builder class that give you a fluent API.

public class WindowRuleBuilder
{
    private IList<IWindowRule> rules;
    public WindowRuleBuilder(IList<IWindowRule> rules = null) =>
        rules = rules ?? new List<IWindowRule>();

    public WindowRuleBuilder AddRule(IWindowRule newRule)
    {
        rules.Add(newRule);
        return this;
    }
    public void Run(string command)
    {
        foreach (IWindowRule rule in rules)
        {
            if (rule.Command == command) rule.Invoke();
        }
    }
}

Now you have something like this:

public static void Main(string[] args)
{
    WindowRuleBuilder ruleBuilder = new WindowRuleBuilder()
        .AddRule(new CloseWindowRule(conrolCommands))
        .AddRule(new ShowAllWindowRule(programCommands))
        .AddRule(new SwitchWindowRule(controlCommands));
    ruleBuilder.Run(args[0]);
}

This is highly extendable as for ever new rule you simply create the class and add it to the rule builder with the AddRule() method. It also doesn't take much reading to understand what's going on here. It's a much more compositional approach. Though I again admit, it does take a bit of work to implement but the code adheres to SOLID and is finely decoupled.

like image 78
Xipooo Avatar answered Oct 18 '22 04:10

Xipooo


You can do this to refactor your switch statement:

var commands = new Dictionary<string, Action>()
{
    { "Show commands", () => ProgramCommans.ShowAllCommands() },
    { "Close window", () => ControlCommands.CloseWindow() },
    { "Switch window", () => ControlCommands.SwitchWindow() },
};

if (commands.ContainsKey(command))
{
    commands[command].Invoke();
}

The main advantage to this approach is that you can change the "switch" at run-time.

like image 40
Enigmativity Avatar answered Oct 18 '22 03:10

Enigmativity