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

问题描述 投票:33回答:21

这个问题的动机是我最近开始经常看到的一些问题,即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,他提供了我不正常的习惯,虽然不建议使用,但是看起来非常有趣。

谢谢大家的答案(到目前为止),我想我今天可能已经学到了什么!

language-agnostic design-patterns
21个回答
21
投票

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


3
投票

在这个简单的例子中,您可以使用开关。

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

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


3
投票

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

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

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


3
投票

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


2
投票

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


2
投票

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


1
投票

在OO范例中,您可以使用良好的旧多态性来完成它。太大的if - else结构或switch结构有时被认为是代码中的气味。


1
投票

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

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

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


1
投票

在python中,我会将您的代码编写为:

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

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)))

(案例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; };

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;
}

这就是你所需要的!它只有一条线,它很漂亮,比开关短,如果!


11
投票

一个开关声明:

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

  case 2:
    doTwo();
    break;

  case 3:
    doThree();
    break;

  default:
    doNone();
    break;
}

0
投票

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


0
投票

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


11
投票

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

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

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

抽象

你的代码是否包含if语句?切换声明?你在不同的地方有相同的开关声明吗?当您进行更改时,您是否发现自己在几个地方进行相同的更改if / switch?你有没有忘记一个?

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


5
投票

这个问题有两个部分。

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

什么时候根据价值发货?每个值只在一个位置:创建一个知道如何为值提供预期行为的多态对象。


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


3
投票

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


3
投票

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


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

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


3
投票

我只是为了好玩而使用以下短手!如果代码清晰度超过输入的字符数,则不要尝试任何这些。

对于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”这个名字:-)

© www.soinside.com 2019 - 2024. All rights reserved.