【问题标题】:What is the best way to replace or substitute if..else if..else trees in programs?在程序中替换或替换 if..else if..else 树的最佳方法是什么?
【发布时间】:2009-02-06 07:27:22
【问题描述】:

这个问题的动机是我最近开始经常看到的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# 中)或 Dictionary 或任何其他类似这样的 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 谈话中的两个有趣点:

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

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

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

【问题讨论】:

    标签: language-agnostic design-patterns


    【解决方案1】:

    这些构造通常可以被多态性所取代。这将为您提供更短且更简洁的代码。

    【讨论】:

    • 多态性很棒,当它可以使用时。循环内部怎么样?我从未见过它在那里使用过。可以吗?
    • @Vadim - 不一定,但如果 if/switch 块重复它可能会。
    • @WolfmanDragon - 当然可以。它只是根据实际运行时类型调用一个方法。
    • 我要过去问这个问题,然后我看不到它。
    • 如果差异值得继承,多态就是答案。否则,我认为不值得担心。如果树木只在浓密时有害,而不是在它们又长又散乱时才有害。
    【解决方案2】:

    在面向对象的语言中,使用多态来替换 if 是很常见的。

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

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

    摘要

    您的代码是否充满了 if 语句? 切换语句?你有没有 不同的 switch 语句 地方?当你做出改变时,你 发现自己在做同样的改变 到相同的 if/switch 在几个 地方?你有没有忘记一个?

    本次演讲将讨论以下方法 使用面向对象技术 删除其中许多条件。这 结果更干净、更紧密、更好 设计的代码更容易测试, 理解和维护。

    【讨论】:

      【解决方案3】:

      一个 switch 语句:

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

      【讨论】:

        【解决方案4】:

        这个问题有两个部分。

        如何根据值进行调度?使用 switch 语句。它最清楚地显示了您的意图。

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

        【讨论】:

          【解决方案5】:

          根据你的事物类型 if..else'ing,考虑创建对象层次结构并使用多态性。像这样:

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

          【讨论】:

            【解决方案6】:

            switch 语句当然比所有那些 if 和 else 更漂亮。

            【讨论】:

              【解决方案7】:

              除了使用 switch 语句,它可以更快,没有。 If Else 清晰易读。必须在地图中查找东西会使事情变得模糊。为什么要让代码更难阅读?

              【讨论】:

                【解决方案8】:
                switch (i) {
                  case 1:  doOne(); break;
                  case 2:  doTwo(); break;
                  case 3:  doThree(); break;
                  default: doNone(); break;
                }
                

                输入此内容后,我必须说您的 if 语句并没有太大问题。正如爱因斯坦所说:“让它尽可能简单,但不要更简单”。

                【讨论】:

                  【解决方案9】:

                  我使用以下简写只是为了好玩!如果您对代码清晰度的关注超过了键入的字符数,请不要尝试其中任何一种。

                  对于 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 更快,而且它确实吓坏了所有新手。想象一下这样的一整页语句,有 5 级缩进。

                  “if”在我的一些代码中很少见,我将其命名为“if-less programming”:-)

                  【讨论】:

                  • 这只是反常的:) +1 仅此而已。
                  【解决方案10】:

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

                  否则基于表格的方法看起来不错,只要条件足够规则以使其适用,尤其是在案例数量很大的情况下,这将是我的第二选择。

                  如果情况不多,条件和行为不规则,多态是一种选择。

                  【讨论】:

                    【解决方案11】:

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

                    我已经看到这种情况发生了很多次(开关嵌套 4 层深,数百行长 - 无法维护),尤其是在试图为太多不同的不相关类型做太多事情的工厂类内部。

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

                    【讨论】:

                      【解决方案12】:

                      使用开关/外壳更清洁:p

                      【讨论】:

                        【解决方案13】:

                        switch 声明或带有虚函数的类作为奇特的解决方案。或指向函数的指针数组。这完全取决于条件的复杂程度,有时无法绕过这些条件。再说一遍,创建一系列类来避免使用一个 switch 语句显然是错误的,代码应该尽可能简单(但不能更简单)

                        【讨论】:

                          【解决方案14】:

                          我什至会说任何程序都不应该使用 else。如果你这样做,你就是在自找麻烦。你永远不应该假设它不是 X,它一定是 Y。你的测试应该单独测试每个测试并在这些测试之后失败。

                          【讨论】:

                          • 这太强了,从问题中可以看出——“else doNone()”当然可能是正确的做法,而不是枚举所有其他可能的值。
                          【解决方案15】:

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

                          【讨论】:

                            【解决方案16】:

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

                            switch 的缺点是缺少 break 语句并失败,并且确实鼓励不要将问题分解成更小的部分。

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

                            【讨论】:

                              【解决方案17】:

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

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

                              【讨论】:

                                【解决方案18】:

                                我将这些 if-elseif-... 结构视为“关键字噪音”。虽然它的作用可能很清楚,但它缺乏简洁性;我认为简洁是可读性的重要组成部分。大多数语言都提供类似switch 的声明。构建地图是一种在没有类似语言的语言中获得类似内容的方法,但它确实感觉像是一种解决方法,并且有一点开销(switch 语句转换为一些简单的比较操作和条件跳转,但是地图首先是内置在内存中,然后是查询,然后才会发生比较和跳转)。

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

                                (条件 ((= i 1) (做一个)) ((= 我 2) (做两个)) ((= 我 3) (做三)) (吨 (无)))

                                (案例我 (1(做一个)) (2(做两个)) (3(做三)) (否则(不做)))

                                当然,您可以根据需要制作自己的 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; };
                                

                                【讨论】:

                                  【解决方案19】:

                                  使用三元运算符!

                                  三元运算符(53个字符):

                                  i===1?doOne():i===2?doTwo():i===3?doThree():doNone();
                                  

                                  如果(108 个字符):

                                  if (i === 1) {
                                  doOne();
                                  } else if (i === 2) {
                                  doTwo();
                                  } else if (i === 3) {
                                  doThree();
                                  } else {
                                  doNone();
                                  }
                                  

                                  切换((甚至比 IF!?!?)114 个字符):

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

                                  这就是你所需要的!它只有一行,而且非常整洁,比 switch 和 if 短得多!

                                  【讨论】:

                                    【解决方案20】:

                                    当然,这个问题与语言有关,但在许多情况下,switch 语句可能是更好的选择。一个好的 C 或 C++ 编译器将能够生成 jump table,这对于大量的情况会明显更快。

                                    【讨论】:

                                      【解决方案21】:

                                      如果你真的必须有一堆 if 测试,并且想要在测试为真时做不同的事情,我会推荐一个只有 if 的 while 循环——没有别的。每个 if 都进行一次测试,然后调用一个方法,然后跳出循环。没有什么比一堆堆积的 if/else/if/else 等更糟糕的了。

                                      【讨论】:

                                        猜你喜欢
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 2014-11-29
                                        • 1970-01-01
                                        • 2013-09-16
                                        • 1970-01-01
                                        • 1970-01-01
                                        • 1970-01-01
                                        相关资源
                                        最近更新 更多