什么是失控开关声明的最佳替代方案?
我inheritance了一个包含一些巨大的switch语句块的项目,其中一些包含多达20个案例。 重写这些的好方法是什么?
你为什么要在不同的结构中重写它们? 如果您确实需要单独处理20个案例,那么就可以使用开关/箱子了。 维持一个if / then逻辑的大链条是可怕的。
如果使用面向对象语言,多态性是另一种选择。 每个子类都将在方法中实现它自己的function。
多态性。 但这可能不是一个微不足道的重构。
一些例子和参考:
重构(Googe书籍)
切换语句代码气味和多态性
重构switch语句
正如其他人所指出的,它取决于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"); }
从switch语句中考虑了tvals,它甚至可以在外部提供,以扩展您可以处理的案例数量。 或者你可以动态构建它。 但在现实世界中,switch语句通常会有特殊情况。 我把它作为读者的练习。
三个建议(回应已经给出的一些):
-
也许开关没有您想象的那么糟糕。 如果案件块很大,那可能会很难看。 通过将逻辑提取到方法中来缩短它们。
-
正如许多人所指出的那样,在OO语言中,多态性可能就是答案。
-
在函数式语言中,如Javascript,编写一个函数,返回运行任何输入所需的函数。 这可能使用switch语句本身,或者它可能使用查找表。
您始终可以使用查找表 。
在switch语句中有20个案例没有错。 您可以通过重构整理代码,并至少将案例处理移动到方法/函数中。
根据switch语句的评估内容,您可能希望使用策略模式对其进行重构。 请查看这篇文章,了解使用单独的类替换枚举值上的开关来处理每个函数的示例。
也可能是使用具有20个案例的开关实际上可能是它的最佳行动方案。 只要它是可读的,并且每个结果清楚地传达了动作是什么,就没有必要真正重构它。
一般来说,我认为你应该只在需要时进行重构,例如当你想要添加更多function时,但是当前的设计不适用于任务。 然后,您应该重构而不添加新function,然后才添加新function。
在其他情况下,不要为重构而烦恼。 在需要时做,否则可能有更重要的事情要做。
如果你真的需要,那么访问者设计模式是一个常见的交换机案例替换,但你应该注意它确实有缺点。 (请查看www.objectmentor.com/resources/articles/acv.pdf )
这取决于switch语句正在做什么。
如果它匹配字符或字符串,比如在解析器中,并且您没有在代码中的任何位置重复相同的模式集,那么switch语句可能没问题。
如果它与允许值列表匹配(比方说)整数,则可以为每个值创建基类和一组派生类。 然后,无论何时生成整数数据,都可以创建派生类的实例,而不是所有的switch语句“answers”。
第三种选择是创建将模式映射到动作的数据结构(即,使用虚方法的函数或对象)。 您可以在此数据结构中查找切换值,并执行相应的操作。
如果它的工作没有重大错误(不是主要的,我的意思是他们不会让你把你的头发拉出来)为什么还要重构呢? 不要重构一切。
如果你愿意,你可以将它改为多态,但这将是一个猎枪手术,你可能不得不重构比这个开关块更多的东西。
访问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
{ public int? Key { get { return 2; } } public void Do() { Sense.Cry cry = new Sense.Cry(); cry.Act(); } } -
ICase只保存一个Key , Switch()函数使用它来导航案例。
public interface ICase
{ TCase Key { get; } }
-
-
创建一个它inheritanceSwitchCase通用抽象类的Condition类。
- 将所有你想要的案例添加到Cases属性中。
-
定义一个Switch()函数并导航Cases属性以查找匹配情况,然后将它们作为条件操作执行。
public class ConditionSense : SwitchCase
{ public ConditionSense() { Cases = new List > { new CaseSmile(), new CaseCry() }; DefaultCases = new List > { new CaseNoSense() }; } public void Switch(int? key) { IEnumerable 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 :-|"); } } }