【问题标题】:Refactor long switch statement重构长 switch 语句
【发布时间】:2013-12-14 17:20:01
【问题描述】:

我是 C# 中的程序,你通过口述命令来控制它,所以现在我有一个很长的 switch 语句。类似的东西

switch (command)

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

等等

几乎所有情况都只调用一种方法,方法不在一个类中,它们分布在许多类中。所以问题是,我怎样才能将这个开关重构为更优雅的方式?

【问题讨论】:

  • 所有函数的签名都一样吗?
  • 这可能更适合codereview.stackexchange.com
  • 每当您发现自己编写的代码使用 字符串 来表示方法时,您首先应该考虑“委托”。
  • 几乎所有不是所有,这可能是保持切换的好理由。
  • 函数有什么共同点吗?

标签: c# .net refactoring switch-statement


【解决方案1】:

您可以这样做来重构您的 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();
}

这种方法的主要优点是您可以在运行时更改“开关”。

【讨论】:

  • 这是一个伟大而简单的方法。我比我更喜欢它。 +1
【解决方案2】:

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

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】:

如果所有函数都获得相同的参数并返回相同的值,您可以使用Dictionarydelegates 将字符串映射到函数。这种方法还将允许您在运行时更改开关——允许外部程序扩展程序的功能。

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

【讨论】:

    【解决方案4】:

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

    以下代码使用自定义属性 (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】:

      我知道答案有点晚了,为了不滥用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();
          }
      }
      

      【讨论】:

        【解决方案6】:

        这是您可以在此处执行的操作。您可以创建一个接口 [ICommand],您可以在其中放置一个常用功能 [eg: Execute]。

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

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

        希望对您有所帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-06-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多