什么是失控开关声明的最佳替代方案?

时间:2009-01-04 06:00:37

标签: c# javascript refactoring switch-statement

我继承了一个包含一些巨大的switch语句块的项目,其中一些包含多达20个案例。什么是重写这些的好方法?

11 个答案:

答案 0 :(得分:16)

为什么要在不同的结构中重写它们?如果您确实有20个案例需要单独处理,那么就可以使用开关/箱子了。维持一个if / then逻辑的大链条。

如果您使用面向对象语言,则多态性是另一种选择。每个子类都将在方法中实现它自己的功能。

答案 1 :(得分:6)

多态性。但这可能不是一个微不足道的重构。

一些例子和参考:

Refactoring (Googe books)

Switch Statement code smell and Polymorphism

Refactoring switch-statements

答案 2 :(得分:5)

正如其他人所指出的,它取决于switch语句。但是,在过去,我通过以下方式继续重构switch语句。假设我们有一个像这样的switch语句,有很多重复的代码

switch(x){
   case 1:
     makeitso("foo");
     globalLog += "foo";
   case 2:
     makeitso("bar");
     globalLog += "bar";
   case 3:
     makeitso("baz");
     globalLog += "baz";
   ...
   default:
      throw("input error");
}

首先要做的是识别常见的部分(在现实世界中,这可能是 little 更实质)

makeitso([some string]);
globalLog += [some string];

并将其转换为函数

function transformInput(somestring) {
     makeitso(somestring);
     globalLog += somestring;
}

然后对于每种情况下更改的部分,使用散列或数组;

var transformvalues = ["foo", "bar", "baz"];

从这里我们可以做到这一点:

var tvals = ["foo", "bar", "baz" ... ];
function transformInput(somestring) {
     makeitso(somestring);
     globalLog += somestring;
}
var tval = tvals[x];
if(tval!==undefined) {
     transformInput(tval);
} else {
    throw ("invalid input");
} 

从switch语句中考虑了tvals,它甚至可以在外部提供,以扩展您可以处理的案例数量。或者你可以动态构建它。但在现实世界中,switch语句通常会有特殊情况。我把它作为读者的练习。

答案 3 :(得分:4)

三条建议(与已经给出的一些建议相呼应):

  1. 也许开关没有您想象的那么糟糕。如果案件块很大,那可能会很难看。通过将逻辑提取到方法中来缩短它们。

  2. 在OO语言中,正如许多人所指出的那样,多态性可能就是答案。

  3. 在函数式语言中,如Javascript,编写一个函数,返回运行任何输入所需的函数。这可能会使用switch语句本身,或者它可能使用查找表。

答案 4 :(得分:2)

您始终可以使用lookup table

答案 5 :(得分:2)

在switch语句中有20个案例没有错。您可以通过重构整理代码,并至少将案例处理移动到方法/函数中。

答案 6 :(得分:2)

根据switch语句的评估内容,您可能希望使用策略模式对其进行重构。请查看this post,了解使用单独的类替换枚举值上的开关以处理每个函数的示例。

使用具有20个案例的开关可能实际上是它的最佳行动方案。只要它是可读的,每个结果都清楚地传达了动作的内容,就没有必要真正重构它。

答案 7 :(得分:2)

一般情况下,我认为您应该只在需要时进行重构,例如当您想要添加更多功能时,但当前的设计不适用于任务。然后,您应该重构而不添加新功能,然后才添加新功能。

在其他情况下,不要为重构而烦恼。在你需要时这样做,否则可能有更重要的事情要做。

如果你真的需要,那么Visitor Design Pattern是一个常见的交换机案例替换,但你应该注意它确实有缺点。 (即查看www.objectmentor.com/resources/articles/acv.pdf

答案 8 :(得分:0)

这取决于switch语句的作用。

如果它匹配字符或字符串,比如说在解析器中,并且你没有在代码中的任何地方重复相同的模式集,那么switch语句可能没问题。

如果它与允许值列表匹配(比方说)整数,则可以为每个值创建基类和一组派生类。然后,无论何时生成整数数据,都可以创建派生类的实例,并使用所有switch语句“answers”。

第三种选择是创建一个数据结构,将模式映射到动作(即,使用虚方法的函数或对象)。您可以在此数据结构中查找切换值,并执行相应的操作。

答案 9 :(得分:0)

如果它没有重大错误(不重要,我的意思是它们不会让你把头发拉出来),为什么还要重构呢?不要重构一切。

如果你愿意,你可以将它改为多态,但这将是一个猎枪手术,你可能不得不重构比这个开关块更多的东西。

答案 10 :(得分:-1)

访问https://github.com/Pedram-Ahmadpour/Switch-Case

客户端

创建条件的实例,然后通过 Switch()函数将条件传递给条件对象。

    int sense = 2;
    ConditionSense conditionSense = new ConditionSense();
    conditionSense.Switch(sense);

服务器端

  1. 创建条件操作。此界面定义了如何执行条件

    public interface IAction
    {
        void Do();
    }
    
  2. 创建所需案例列表。这些类必须实现条件操作 ICase ;让它们保持清淡。

        public class CaseCry : IAction, ICase<int?>
        {
            public int? Key { get { return 2; } }
    
            public void Do()
            {
                Sense.Cry cry = new Sense.Cry();
                cry.Act();
            }
        }
    
    • ICase 只包含一个 Switch()函数用它来导航案例。

      public interface ICase<TCase>
      {
          TCase Key { get; }
      }
      
  3. 创建一个条件类,它继承 SwitchCase 通用抽象类。

    • 将您想要的所有案例添加到案例属性。
    • 定义 Switch()功能并导航 Cases 属性以查找匹配案例,然后将其作为条件操作执行。

      public class ConditionSense : SwitchCase<int?>
      {
          public ConditionSense()
          {
              Cases = new List<ICase<int?>>
              {
                  new CaseSmile(),
                  new CaseCry()
              };
      
              DefaultCases = new List<ICase<int?>> {
                  new CaseNoSense()
              };
          }
      
          public void Switch(int? key)
          {
              IEnumerable<IAction> matches = Cases.Where(p => p.Key.Equals(key))
                  .Select(p => p as IAction);
              if (matches.Count() > 0)
                  foreach (IAction match in matches)
                      match.Do();
              else
                  foreach (IAction defaultCase in DefaultCases)
                      defaultCase.Do();
          }
      }
      
  4. 微笑哭泣 ...,可以是巨大的,不要担心它们的大小; 条件操作 ICase 让它们延迟加载。

            public class Sense
            {
                public class Smile
                {
                    public void Act()
                    {
                        Console.WriteLine("I'm smiling :-)");
                    }
                }
    
                public class Cry
                {
                    public void Act()
                    {
                        Console.WriteLine("I'm crying :-(");
                    }
                }
    
                public class NoSense
                {
                    public void Act()
                    {
                        Console.WriteLine("I've no sense :-|");
                    }
                }
            }