一个失控的switch语句有什么最好的替代方案?

11

我继承了一个项目,其中包含一些大型的switch语句块,有些包含多达20个case。如何重写这些代码?


这些代码块之间有多少重复代码? - Anthony
11个回答

16

为什么要以不同的结构重写它们?如果你真的有20个必须单独处理的情况,那么使用switch/case是最好的选择。一个庞大的if/then逻辑链将难以维护。

如果你正在使用面向对象的语言,多态性(polymorphism)是另一个选项。每个子类都会在方法中实现自己的功能。


多态性就这样了,是吗? - Mitch Wheat
是的,问题在于代码中有一个点可能会发生20种不同的事情,而不是switch语句本身。在这种棘手的情况下,switch语句是最好的结构。 - jrcs3
如果代码是用C或其他非面向对象的语言编写的,那么多态性实际上不是一个选项。 - Dana Robinson
3
现在我看到这个问题有C#和Javascript标签。在C#中,我会考虑多态性。在Javascript中,面向对象编程是非常糟糕的,我会继续使用switch/case :P。 - Dana Robinson
我同意,我有一个项目,里面只有一个巨大的“switch”语句 - 对我来说很有效。 - Dmitri Nesteruk
即使使用多态,这是否仍然不能消除对开关的需求?您仍需要在工厂内部使用开关来选择正确的实现。但是,它将从开关中消除所有逻辑,并将其抽象到工厂中,而不是将其放在主要流程中。我提出这个问题只是因为我经常看到这种多态答案,但从未有过例子。 - Sinaesthetic

6

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

首先要做的是识别那些共同的部分(在现实世界中,这可能会更加实质性一些)

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

如果将tvals从switch语句中分离出来,甚至可以在外部提供它以扩展您能够处理的情况数量。或者您可以动态构建它。然而,在现实世界中,switch语句通常会有特殊情况。这留给读者作为一个练习。


4

以下是三个建议(与已经给出的一些建议相呼应):

  1. 也许开关不像你想象的那么糟糕。如果case块很大,则可能会很丑陋。通过将逻辑提取到方法中来缩短它们。

  2. 在面向对象的语言中,多态可能是答案,正如许多人所指出的那样。

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


2

2

在switch语句中有20个case并没有问题。您可以通过重构代码来整理它,并且至少将case处理移到方法/函数中。


2
根据 switch 语句所评估的内容,您可能希望使用策略模式对其进行重构。请参考此文章,了解使用单独的类来处理每个函数替换枚举值开关的示例。
同时,如果使用 20 个 case 的开关实际上是最佳选择,那么也可以继续使用。只要它易读且每个结果都清楚地传达了操作,就不需要真正重构它。

2
总的来说,我认为只有在需要时才应该重构,例如当您想要添加更多功能,但当前的设计无法完成任务时。然后,您应该在不添加新功能的情况下进行重构,然后再添加新功能。
在其他情况下,不要费心进行重构。除非需要,否则可能有更重要的事情要做。
如果您确实需要重构,那么访问者模式是常见的开关语句替代方案,尽管您应该注意它确实有缺点(即,请查看www.objectmentor.com/resources/articles/acv.pdf)。

0

这取决于switch语句的作用。

如果它匹配字符或字符串,比如在解析器中,并且您没有在代码中重复使用相同的模式集,则switch语句可能是可以接受的。

如果它匹配(比如)整数与允许值列表,则可以为每个值创建一个基类和一组派生类。然后,无论是什么生成了第一次的整数数据,都可以创建一个带有所有switch语句“答案”的派生类实例。

第三个选项是创建一个将模式映射到操作(即具有虚拟方法的函数或对象)的数据结构。您可以在此数据结构中查找switch值,并执行适当的操作。


0

如果程序没有重大错误(我指的是不会让你抓狂的错误),为什么要重构它呢?不要对所有东西都进行重构。

如果你想的话,可以将其更改为多态性,但这将是一种散弹手术,你可能需要重构比这个 switch 块更多的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接