在程序中替换或替换if..else if..else树的最佳方法是什么?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在程序中替换或替换if..else if..else树的最佳方法是什么?相关的知识,希望对你有一定的参考价值。
这个问题的动机是我最近开始经常看到的一些问题,即if..else if..else
结构。虽然它很简单并且有其用途,但它的一些东西一直在告诉我它可以用更细粒度,优雅且通常更容易保持最新的东西代替。
为了尽可能具体,这就是我的意思:
if (i == 1) {
doOne();
} else if (i == 2) {
doTwo();
} else if (i == 3) {
doThree();
} else {
doNone();
}
我可以想到两种简单的方法来重写它,或者通过三元(这只是编写相同结构的另一种方式):
(i == 1) ? doOne() :
(i == 2) ? doTwo() :
(i == 3) ? doThree() : doNone();
或使用Map(在Java中,我认为在C#中)或字典或任何其他K / V结构,如下所示:
public interface IFunctor() {
void call();
}
public class OneFunctor implemets IFunctor() {
void call() {
ref.doOne();
}
}
/* etc. */
Map<Integer, IFunctor> methods = new HashMap<Integer, IFunctor>();
methods.put(1, new OneFunctor());
methods.put(2, new TwoFunctor());
methods.put(3, new ThreeFunctor());
/* .. */
(methods.get(i) != null) ? methods.get(i).call() : doNone();
事实上,上面的Map方法是我最后一次做的,但是现在我不能不再想到这个问题必须有更好的替代方案。
那么,哪个其他 - 并且最有可能更好的方法来替换if..else if..else在那里,哪一个是你最喜欢的?
你的想法低于这条线!
好的,这是你的想法:
首先,最流行的答案是switch语句,如下所示:
switch (i) {
case 1: doOne(); break;
case 2: doTwo(); break;
case 3: doThree(); break;
default: doNone(); break;
}
这仅适用于可以在交换机中使用的值,至少在Java中这是一个非常有限的因素。但是,对于简单的情况,自然可以接受。
你似乎建议的另一个也许有点更有趣的方法是使用多态。由CMS链接的Youtube讲座是一款出色的手表,请在此处查看:"The Clean Code Talks -- Inheritance, Polymorphism, & Testing"据我所知,这将转化为以下内容:
public interface Doer {
void do();
}
public class OneDoer implements Doer {
public void do() {
doOne();
}
}
/* etc. */
/* some method of dependency injection like Factory: */
public class DoerFactory() {
public static Doer getDoer(int i) {
switch (i) {
case 1: return new OneDoer();
case 2: return new TwoDoer();
case 3: return new ThreeDoer();
default: return new NoneDoer();
}
}
}
/* in actual code */
Doer operation = DoerFactory.getDoer(i);
operation.do();
谷歌谈话的两个有趣点:
- 使用Null对象而不是返回空值(请仅抛出运行时异常)
- 尝试编写一个没有if:s的小项目。
此外,在我看来,值得一提的一个帖子是CDR,他提供了我不正常的习惯,虽然不建议使用,但是看起来非常有趣。
谢谢大家的答案(到目前为止),我想我今天可能已经学到了什么!
这些结构通常可以被多态性取代。这将为您提供更短,更简单的代码。
在这个简单的例子中,您可以使用开关。
否则,基于表格的方法看起来很好,只要条件足够常规以使其适用,这将是我的第二选择,特别是当案例数量很大时。
如果没有太多的情况,并且条件和行为是不规则的,那么多态性将是一种选择。
问题中给出的例子非常简单,可以使用简单的开关。当if-elses嵌套越来越深时,就会出现问题。它们不再“清晰或易于阅读”(正如其他人所说)并且添加新代码或修复其中的错误变得越来越难以确定,因为如果逻辑逻辑可能不会达到预期的水平很复杂。
我已经看到这种情况发生了很多次(交换机嵌套4层深,数百行长 - 不可能维护),特别是在工厂类中试图为太多不同的不相关类型做太多。
如果您要比较的值不是无意义的整数,而是某种唯一标识符(即使用枚举作为穷人的多态),那么您希望使用类来解决问题。如果它们只是数值,那么我宁愿使用单独的函数来替换if和else块的内容,而不是设计某种人工类层次结构来表示它们。最终会导致比原始意大利面更乱码。
使用更清洁的开关/外壳:p
switch
语句或具有虚函数的类作为花哨的解决方案。或者函数指针数组。这完全取决于条件的复杂程度,有时候如果不是这样的话。再次,创建一系列类以避免一个switch语句显然是错误的,代码应该尽可能简单(但不是更简单)
我甚至会说没有程序应该使用其他程序。如果你这样做,你就是在寻找麻烦。你永远不应该假设它不是X,它必须是Y.你的测试应该单独测试每个测试并且不能进行测试。
在OO范例中,您可以使用良好的旧多态性来完成它。太大的if - else结构或switch结构有时被认为是代码中的气味。
Map方法是最好的。它允许您封装语句并很好地分解。多态性可以补充它,但它的目标略有不同。它还引入了不必要的类树。
开关具有缺少break语句和缺失的缺点,并且真的鼓励不将问题分解成更小的部分。
话虽这么说:if..else的一棵小树很好(事实上,我赞成几天有3个if..elses而不是最近使用Map)。当你开始在其中加入更复杂的逻辑时,由于可维护性和可读性而成为一个问题。
在python中,我会将您的代码编写为:
actions = {
1: doOne,
2: doTwo,
3: doThree,
}
actions[i]()
我认为这些if-elseif -...构造为“关键字噪音”。虽然可能很清楚它的作用,但缺乏简洁性;我认为简洁是可读性的重要部分。大多数语言都提供类似switch
语句的内容。构建一个地图是一种在没有这种语言的情况下得到类似东西的方法,但它确实感觉像是一种解决方法,并且有一些开销(一个switch语句转换为一些简单的比较操作和条件跳转,但是一个映射首先内置在内存中,然后查询,然后才进行比较和跳转)。
在Common Lisp中,有两个内置的开关结构,cond
和case
。 cond
允许任意条件,而case
只测试相等,但更简洁。
(cond ((= i 1) (do-one)) ((= i 2) (do-two)) ((= i 3) (do-three)) (t (do-none)))(案例i(1(do-one))(2(do-two))(3(do-three))(否则(do-none)))
当然,您可以根据自己的需要制作自己的case
宏。
在Perl中,您可以使用for
语句,可选择使用任意标签(此处:SWITCH
):
SWITCH: for ($i) {
/1/ && do { do_one; last SWITCH; };
/2/ && do { do_two; last SWITCH; };
/3/ && do { do_three; last SWITCH; };
do_none; };
使用三元运算符!
三元运算符(53个字符):
i===1?doOne():i===2?doTwo():i===3?doThree():doNone();
如果(108Characters):
if (i === 1) {
doOne();
} else if (i === 2) {
doTwo();
} else if (i === 3) {
doThree();
} else {
doNone();
}
开关((甚至比以下更长!?!?)114字符):
switch (i) {
case 1: doOne(); break;
case 2: doTwo(); break;
case 3: doThree(); break;
default: doNone(); break;
}
这就是你所需要的!它只有一条线,它很漂亮,比开关短,如果!
一个开关声明:
switch(i)
{
case 1:
doOne();
break;
case 2:
doTwo();
break;
case 3:
doThree();
break;
default:
doNone();
break;
}
当然,这个问题依赖于语言,但在许多情况下,switch语句可能是更好的选择。一个好的C或C ++编译器将能够生成一个jump table,对于大型案例,这将显着更快。
如果你真的必须有一堆if测试,并且想要在测试为真时做不同的事情,我会推荐一个while循环,只有ifs-别的。每个if如果测试调用方法然后突破循环。没有别的事情比一堆堆叠的if / else / if / else等更糟糕。
在面向对象的语言中,通常使用多态来替换if。
我喜欢这个涵盖主题的Google Clean Code Talk:
The Clean Code Talks -- Inheritance, Polymorphism, & Testing
抽象
你的代码是否包含if语句?切换声明?你在不同的地方有相同的开关声明吗?当您进行更改时,您是否发现自己在几个地方进行相同的更改if / switch?你有没有忘记一个?
本演讲将讨论使用面向对象技术去除许多条件的方法。结果是更清晰,更紧凑,设计更好的代码,更易于测试,理解和维护。
这个问题有两个部分。
如何根据价值发货?使用switch语句。它最清楚地显示您的意图。
什么时候根据价值发货?每个值只在一个位置:创建一个知道如何为值提供预期行为的多态对象。
根据你所使用的东西的类型,考虑创建一个对象的层次结构并使用多态。像这样:
class iBase
{
virtual void Foo() = 0;
};
class SpecialCase1 : public iBase
{
void Foo () {do your magic here}
};
class SpecialCase2 : public iBase
{
void Foo () {do other magic here}
};
然后在你的代码中只需调用p-> Foo(),就会发生正确的事情。
当然,switch声明比所有那些和其他人更漂亮。
在使用switch语句之外,可以更快,没有。如果Else清晰易读。不得不在地图中查看事物会混淆事物。为什么让代码更难阅读?
switch (i) {
case 1: doOne(); break;
case 2: doTwo(); break;
case 3: doThree(); break;
default: doNone(); break;
}
键入后,我必须说你的if语句没有那么多错。就像爱因斯坦所说的那样:“尽可能简单,但不要简单”。
我只是为了好玩而使用以下短手!如果代码清晰度超过输入的字符数,则不要尝试任何这些。
对于doX()始终返回true的情况。
i==1 && doOne() || i==2 && doTwo() || i==3 && doThree()
当然,我试图确保大多数void函数返回1只是为了确保这些短手是可能的。
您还可以提供作业。
i==1 && (ret=1) || i==2 && (ret=2) || i==3 && (ret=3)
喜欢而不是写作
if(a==2 && b==3 && c==4){
doSomething();
else{
doOtherThings();
}
写
a==2 && b==3 && c==4 && doSomething() || doOtherThings();
在不确定函数将返回的情况下,添加|| 1 :-)
a==2 && b==3 && c==4 && (doSomething()||1) || doOtherThings();
我仍然发现键入比使用所有if-else更快,它肯定会吓跑所有新的noobs。想象一下这样一个完整的声明,有5个级别的缩进。
“if”在我的一些代码中很少见,我给它起了“if-less programming”这个名字:-)
以上是关于在程序中替换或替换if..else if..else树的最佳方法是什么?的主要内容,如果未能解决你的问题,请参考以下文章
switch 和 if else if else 有什么区别
If / else and while分支在Mono.Cecil中