我继承了一个项目,该项目有一些巨大的 switch 语句块,其中一些包含多达 20 个案例。重写这些的好方法是什么?
11 回答
为什么要以不同的结构重写它们?如果你真的有 20 个案例需要单独处理,那么 switch/case 就是要走的路。一大串 if/then 逻辑很难维护。
如果您使用面向对象的语言,多态是另一种选择。每个子类都会在方法中实现它自己的功能。
正如其他人指出的那样,这取决于 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 语句通常会有特殊情况。我把它作为练习留给读者。
三个建议(与已经给出的一些建议相呼应):
也许开关没有你想象的那么糟糕。如果案例块很大,它可能会很难看。通过将逻辑提取到方法中来缩短它们。
正如许多人所指出的,在 OO 语言中,多态性可能是答案。
在函数式语言(如 Javascript)中,编写一个函数,该函数返回您需要为任何输入运行的函数。这可能使用 switch 语句本身,或者它可能使用查找表。
一般来说,我认为你应该只在需要时进行重构,例如当你想添加更多功能时,但当前的设计不能胜任这项任务。然后,您应该在不添加新功能的情况下进行重构,然后才添加新功能。
在其他情况下,不要费心重构。当你需要的时候去做,否则可能有更重要的事情要做。
如果你真的需要,那么访问者设计模式是一种常见的 switch-case 替换,尽管你应该注意到它确实有缺点。(即,查看www.objectmentor.com/resources/articles/acv.pdf)
在 switch 语句中包含 20 个 case 并没有错。您可以通过重构来整理代码,并且至少将案例处理移动到方法/函数中。
根据 switch 语句正在评估的内容,您可能希望使用策略模式对其进行重构。查看这篇文章,了解使用单独的类替换枚举值上的开关以处理每个函数的示例。
也可能是使用带有 20 个案例的开关实际上可能是它的最佳操作方案。只要它是可读的并且每个结果都清楚地传达了动作是什么,就不需要真正重构它。
您总是可以使用查找表。
这取决于 switch 语句在做什么。
如果它匹配字符或字符串,比如在解析器中,并且您没有在代码中到处重复相同的模式集,那么 switch 语句可能没问题。
如果它匹配(例如)一个整数与允许值列表,您可以为每个值创建一个基类和一组派生类。然后,首先生成整数数据的任何东西都可以使用所有 switch 语句“答案”创建派生类的实例。
第三种选择是创建将模式映射到操作(即,具有虚拟方法的函数或对象)的数据结构。您可以在此数据结构中查找开关值,并执行相应的操作。
如果它在没有重大错误的情况下工作(不是重大错误,我的意思是它们不会让你把头发拉出来)为什么还要重构它呢?不要重构一切。
如果您愿意,您可以将其更改为多态,但这将是一次霰弹枪手术,您可能需要重构的不仅仅是这个开关块。
访问https://github.com/Pedram-Ahmadpour/Switch-Case
客户端
创建Condition的实例,然后通过Switch()函数将条件传递给Condition对象。
int sense = 2;
ConditionSense conditionSense = new ConditionSense();
conditionSense.Switch(sense);
服务器端
创建条件动作。该接口定义了如何执行Condition。
public interface IAction { void Do(); }
创建您想要的案例列表。这些类必须实现条件动作和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只保存一个Key,Switch()函数使用它来导航案例。
public interface ICase<TCase> { TCase Key { get; } }
创建一个继承SwitchCase通用抽象类的Condition类。
- 将您想要的所有案例添加到案例属性。
定义一个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(); } }
微笑、哭泣……可以很大,不用担心它们的大小;条件动作和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 :-|");
}
}
}