在程序中替换或替换if..else if..else树的最佳方法是什么?

时间:2009-02-06 07:27:23

标签: language-agnostic design-patterns

这个问题的动机是我最近开始看到的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();

Google演讲的两个有趣点:

  • 使用Null对象而不是返回空值(并且只能抛出运行时异常)
  • 尝试编写一个没有if:s。
  • 的小项目

此外,我认为值得一提的一个帖子是CDR,他提供了我不正常的习惯,虽然不建议使用,但看起来非常有趣。

谢谢大家的回答(到目前为止),我想我今天可能已经学会了一些东西!

21 个答案:

答案 0 :(得分:21)

这些结构通常可以被多态性取代。这将为您提供更短,更简单的代码。

答案 1 :(得分:11)

开关声明:

switch(i)
{
  case 1:
    doOne();
    break;

  case 2:
    doTwo();
    break;

  case 3:
    doThree();
    break;

  default:
    doNone();
    break;
}

答案 2 :(得分:11)

在面向对象语言中,通常使用多态来替换if。

我喜欢这个涵盖主题的Google Clean Code Talk:

The Clean Code Talks -- Inheritance, Polymorphism, & Testing

  

摘要

     

您的代码是否包含if语句?   切换声明?你有吗?   各种各样的开关声明   地方呢?当你做出改变时,你做到了   发现自己做出了同样的改变   相同的if /切换几个   地方呢?你有没有忘记一个?

     

本演讲将讨论的方法   使用面向对象技术   删除许多这些条件。该   结果更清洁,更紧凑,更好   设计的代码更容易测试,   理解和维护。

答案 3 :(得分:5)

这个问题有两个部分。

如何根据值发送?使用switch语句。它最清楚地显示您的意图。

何时根据值调度?每个值只在一个位置:创建一个知道如何为值提供预期行为的多态对象。

答案 4 :(得分:5)

根据你所使用的东西的类型,考虑创建对象的层次结构并使用多态。像这样:

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-&gt; Foo(),就会发生正确的事情。

答案 5 :(得分:3)

switch声明当然比所有那些和其他人更漂亮。

答案 6 :(得分:3)

问题中给出的示例足以使用简单的开关。当if-elses嵌套越来越深时,就会出现问题。它们不再“清晰或易于阅读”(正如其他人所说)并且添加新代码或修复其中的错误变得越来越难以确定,因为如果逻辑逻辑可能不会达到预期的水平很复杂。

我已经看到这种情况发生了很多次(交换机嵌套了4层深度和数百行长 - 无法维护),特别是在工厂类中试图为太多不同的不相关类型做太多。< / p>

如果您所比较的值不是无意义的整数,而是某种唯一标识符(即使用枚举作为穷人的多态性),那么您希望使用类来解决问题。如果它们只是数值,那么我宁愿使用单独的函数来替换if和else块的内容,而不是设计某种人工类层次结构来表示它们。最终会导致代码比原始意大利面条更加混乱。

答案 7 :(得分:3)

我只是为了好玩而使用以下短手!如果代码清晰度比输入的字符数更多,请不要尝试其中任何一种。

对于doX()始终返回true的情况。

i==1 && doOne() || i==2 && doTwo() || i==3 && doThree()

当然,我试图确保大多数虚空函数返回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”: - )

答案 8 :(得分:3)

switch (i) {
  case 1:  doOne(); break;
  case 2:  doTwo(); break;
  case 3:  doThree(); break;
  default: doNone(); break;
}

输入后,我必须说你的if语句没有那么多错。就像爱因斯坦所说的那样:“尽可能简单,但不要简单”。

答案 9 :(得分:3)

使用switch语句之外,可以更快,没有。如果Else清晰易读。不得不在地图中查看事物会混淆事物。为什么让代码更难阅读?

答案 10 :(得分:3)

在这种简单的情况下,您可以使用开关。

否则基于表格的方法看起来很好,只要条件足够常规以使其适用,这将是我的第二选择,特别是当案例数量很大时。

如果没有太多的情况,条件和行为是不规则的,那么多态性将是一种选择。

答案 11 :(得分:3)

使用更清洁的开关/盒子:p

答案 12 :(得分:2)

switch语句或具有虚函数的类作为花哨的解决方案。或者函数指针数组。这完全取决于条件的复杂程度,有时候如果不是这样的话。再次,创建一系列类以避免一个switch语句显然是错误的,代码应该尽可能简单(但不是更简单)

答案 13 :(得分:2)

我甚至会说没有程序应该使用其他程序。如果你这样做,你就是在寻找麻烦。你永远不应该假设它不是X它必须是Y.你的测试应该单独测试每个测试并且在测试之后失败。

答案 14 :(得分:1)

在OO范例中,你可以使用良好的旧多态来做到这一点。太大的if - else结构或switch结构有时被认为是代码中的气味。

答案 15 :(得分:1)

Map方法是最好的。它允许您封装语句并很好地分解。多态性可以补充它,但它的目标略有不同。它还引入了不必要的类树。

开关具有缺少break语句的缺点,并且真的鼓励不将问题分解成更小的部分。

话虽这么说:if..else的一棵小树很好(事实上,我赞成几天有3个if..elses而不是最近使用Map)。当你开始在其中加入更复杂的逻辑时,由于可维护性和可读性而成为一个问题。

答案 16 :(得分:1)

在python中,我会把你的代码写成:

actions = {
           1: doOne,
           2: doTwo,
           3: doThree,
          }
actions[i]()

答案 17 :(得分:1)

我将这些if-elseif -...构造视为“关键字噪音”。虽然可能很清楚它的作用,但缺乏简洁性;我认为简洁是可读性的重要部分。大多数语言都提供类似switch语句的内容。构建一个地图是一种在没有这种语言的情况下得到类似东西的方法,但它确实感觉像是一种解决方法,并且有一些开销(一个switch语句转换为一些简单的比较操作和条件跳转,但是一个映射第一个是内置的,然后查询,然后才进行比较和跳转。

在Common Lisp中,内置了两个开关构造condcasecond允许任意条件,而case仅测试相等性,但更简洁。

(cond ((= i 1)
       (do-one))
      ((= i 2)
       (do-two))
      ((= i 3)
       (do-three))
      (t
       (do-none)))

(case i (1 (do-one)) (2 (do-two)) (3 (do-three)) (otherwise (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; };

答案 18 :(得分:1)

使用三元运算符!

三元运算符(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;
}

这就是你所需要的!它只有一条线,它非常整洁,比开关短,如果!

答案 19 :(得分:0)

当然,这个问题依赖于语言,但在许多情况下,switch语句可能是更好的选择。一个好的C或C ++编译器将能够生成jump table,这对于大型案例集来说会明显更快。

答案 20 :(得分:0)

如果你真的必须有一堆if测试,并且想要在测试为真时做不同的事情,我会推荐一个while循环,只有ifs-别的。每个if如果测试调用方法然后突破循环。没有别的东西比一堆堆叠的if / else / if / else等更糟糕。