重构长切换语句

时间:2013-12-14 17:20:01

标签: c# .net refactoring switch-statement

我在c#中编程,你通过口述命令控制,所以现在我有一个很长的switch语句。像

这样的东西
switch (command)

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

等等

几乎所有情况都只调用一个方法,方法不在一个类中,而是分布在许多类中。所以问题是,我如何能够以更优雅的方式重构这种转变?

6 个答案:

答案 0 :(得分:8)

您可以这样做来重构您的switch语句:

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();
}

这种方法的主要优点是你可以改变&#34;开关&#34;在运行时。

答案 1 :(得分:1)

如果所有函数都获得相同的参数并返回相同的值,则可以使用Dictionarydelegates将字符串映射到函数。此方法还允许您更改交换机的运行时间 - 允许外部程序扩展程序的功能。

如果函数不相同,你可以编写包装器 - 一个代理函数,它将获取所有其他函数的参数,并调用你想要的函数。

答案 2 :(得分:1)

我更喜欢使用策略模式来扩展切换案例语句。首先,我创建一个接口,定义每个规则的外观:

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

然后创建一个实现每种可能情况的接口的类:

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();
}

然后您的switch语句变成这样:

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

现在,您可以向函数传递所需的任何规则集并运行它们,以使函数遵循“打开/关闭”原则。

我意识到这似乎有点工程过度,而且我确实认为有更多的功能解决方案需要的工作量少一些,但是这样做还有一个额外的好处,即允许您通过创建注入的类来扩展此功能。各种情况下的规则列表,甚至制作出可以为您提供流畅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();
        }
    }
}

现在您有类似这样的内容:

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]);
}

这是高度可扩展的,因为对于任何新规则,您只需创建类并使用AddRule()方法将其添加到规则构建器中即可。不需要太多的阅读就可以了解这里发生的事情。这是一种更加组合的方法。尽管我再次承认,要实现它确实需要花费一些工作,但是代码遵循SOLID并已很好地分离。

答案 3 :(得分:0)

这是你在这里可以做的。您可以创建一个接口[ICommand],您可以在其中放置一个公共函数[例如:执行]。

然后您只需要使用适当的类型启动该成员并调用Execute函数。这可能包括未来的更多功能,因此得到了扩展。

此外,您可以创建一个工厂方法,您可以在其中传递参数并获取适当的类。

希望有所帮助。

答案 4 :(得分:0)

我意识到这是一个老帖子,但在这些情况下,我发现属性和工厂非常方便。

以下代码使用自定义属性(Command)来允许您对方法进行属性化,并提供它们应如何响应您的字符串值。

工厂方法使用反射生成这些方法的字典,并在您调用CommandFactory时调用它。

事情可能会被清理一下,调用invoke有点难看,但这取决于你想如何执行代码。

using System.Collections.Generic;
using System.Linq;

namespace MyApp
{
    using System.Reflection;
    using MyApp.Commands;

    class Program
    {
        static void Main(string[] args)
        {
            var methods = new MyCommands();
            MethodInfo myMethod;
            myMethod = CommandFactory.GetCommandMethod("Show Commands");
            myMethod.Invoke(methods, null);
            myMethod = CommandFactory.GetCommandMethod("Close window");
            myMethod.Invoke(methods, null);
            myMethod = CommandFactory.GetCommandMethod("Switch window");
            myMethod.Invoke(methods, null);
        }
    }

    public static class CommandFactory
    {
        private static Dictionary<string, MethodInfo> speechMethods = new Dictionary<string, MethodInfo>();
        public static MethodInfo GetCommandMethod(string commandText)
        {
            MethodInfo methodInfo;
            var commands = new MyCommands();
            if (speechMethods.Count == 0)
            {
                var methodNames =
                    typeof(MyCommands).GetMethods(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance);
                var speechAttributeMethods = methodNames.Where(y => y.GetCustomAttributes().OfType<CommandAttribute>().Any());
                foreach (var speechAttributeMethod in speechAttributeMethods)
                {
                    foreach (var attribute in speechAttributeMethod.GetCustomAttributes(true))
                    {
                        speechMethods.Add(((CommandAttribute)attribute).Command, speechAttributeMethod);
                    }
                }
                methodInfo = speechMethods[commandText];
            }
            else
            {
                methodInfo = speechMethods[commandText];
            }
            return methodInfo;
        }
    }
}

namespace MyApp.Commands
{
    class MyCommands
    {
        [Command("Show All")]
        [Command("Show All Commands")]
        [Command("Show commands")]
        public void ShowAll()
        {
            ProgramCommands.ShowAllCommands();
        }

        [Command("Close Window")]
        public void CloseWindow()
        {
            ControlCommands.CloseWindow();
        }

        [Command("Switch Window")]
        public void SwitchWindow()
        {
            ControlCommands.SwitchWindow();
        }
    }

    [System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = true)]
    public class CommandAttribute : System.Attribute
    {
        public string Command
        {
            get;
            set;
        }

        public CommandAttribute(string textValue)
        {
            this.Command = textValue;
        }
    }
}

答案 5 :(得分:0)

我知道答案有点晚,为了不滥用SOLID原则,你可以使用接口或继承。在这个例子中,我使用继承,因为你可能有其他用法的“命令”字符串。

public abstract class commandRepository {
    string command ; // if there is no usage in other function class, you can get rid of it 
    public abstract void DoCommands();
}
public class ShowCommands:commandRepository 
{
    public ShowCommands (){
        command ="Show commands";   // if there is no usage in other function class, you can get rid of it
    }
    public override void DoCommands(){
        ProgramCommans.ShowAllCommands();
    }
}
public class CloseWindow:commandRepository 
{

    public CloseWindow (){
        command ="Close window";    // if there is no usage in other function class, you can get rid of it
    }
    public override void DoCommands(){
        ProgramCommans.CloseWindow();
    }
}
public class SwitchWindow:commandRepository 
{
    public SwitchWindow (){
        command ="Switch window";   // if there is no usage in other function class, you can get rid of it
    }
    public override void DoCommands(){
        ProgramCommans.SwitchWindow();
    }
}