在大 if/else 结构中调用 Functions/class 的好方法

Good way of calling Functions/class in big if/else constructs

我在大多数旧项目(java 大部分)中看到许多看起来像

的代码部分
if(type == typeOne){
    callFunctionOne();
}else if (type == typeTwo){
   callFunctionTwo();
}else if (type == typeThree){
   callFunctionThree();
}//i've seen over ~800 lines like this!

其中 "type" 可以是一个枚举或任何东西,整个事情也可以写成 switch/case 风格。 我的问题是:是否有 "better"(更多 stylish/shorter/more 可读)方法来实现这一点? 我在 PHP 中看到过这样的结构:

//where $type = "one","two" etc.
$functionName = 'callFunction' . $type; 
new $functionName();

但我不确定这是否真的是 "better" 方式,以及它在其他语言中是否可行。

我觉得更有趣的问题是你到底想通过这个实现什么?

Java 是一种面向对象的语言。因此,我将通过每个类型一个 subclass 来解决这个问题:

abstract class Type{
     abstract void method();
}

class Type1 extends Type{
     void method(){
         //do sth. specific for this type
     }
}

如果这些方法实际上都在同一个 class 中,您仍然可以通过简单地传递自己来从这些 class 中调用它们(我知道这可能会变得丑陋)。

class RandomClass(){
     void method1(){
         //do sth for type1
     }
     void method2(){
         //do sth for type2
     }
}

abstract class Type{
     RandomClass randomClass;
     Type(RandomClass randomClass){
         this.randomClass = randomClass;
     }
     abstract void method();
}

class Type1 extends Type{
     void method(){
         randomClass.method1();
     }
}
class Type2 extends Type{
     void method(){
         randomClass.method2();
     }
}

否则你可以使用反射,就像 Sohaib 所建议的(示例取自他的suggested link):

Yyyy.class.getMethod("methodName").invoke(someArgs)

但是像这样使用反射似乎很不方便,因为它性能不佳并且是以后维护的一个很好的陷阱(想象一下有人开始重命名这些方法)。

所以回答问题本身(至少我是如何理解的):

动态调用方法,例如通过在运行时动态确定它们的名称,这是您只能在脚本语言中做的事情。 面向对象的方法可能会带来开销,但最终是这种语言的更好风格。

如果这两种解决方案都不适合您,switch 语句或 if-else 级联是您的最佳选择。

如评论中所述,有多种方法可以使用 java 的反射功能。 See this question for how to do that。也就是说,反射在 java 中是一种非常糟糕的风格,只有在你真的别无选择时才应该使用。 Java 在 OO 风格的编程和静态类型检查方面非常重要,而使用反射却忽略了这两个重点。这样做可能会使您的代码变得同样复杂并且更难调试。

如果没有反射,如果有问题的代码块恰好出现一次,您也无能为力。您必须在某处实现逻辑,可能涉及相同的 if-else/switch 块。但是,如果您发现自己在多个地方复制粘贴相同的 if-elseif-elseif-elseif.... 块,您可以做得更好。

如果 type 是枚举,您可以将逻辑移至枚举本身,从面向对象的角度来看,这非常好。考虑以下因素:

public enum Direction {
    NORTH,
    SOUTH,
    EAST,
    WEST
}

public class Foo {

   public void bar(Direction d) {

       //At some point we want some logic to depend on the vector dx,dy form of d
       int dx = 0;
       int dy = 0;
       switch(d) {
           case NORTH: 
               dy = -1;
               break;
           case SOUTH: 
               dy = 1;
               break;
           case EAST: 
               dx = 1;
               break;
           case WEST: 
               dx = -1;
               break;
        }

        //Use the values in dx, dy
    }
}

围绕您的项目复制粘贴此块显然不是一个好主意。如果你添加了一个新的方向,你必须 return 到每个这样的块来添加正确的添加。从面向对象的角度来看,dx、dy 字段确实是枚举值的一部分,并且一开始就应该是枚举值的一部分。因此,我们可以将上面的内容更改为以下内容:

public enum Direction {
    NORTH,
    SOUTH,
    EAST,
    WEST;

    public int getDX() {
        switch(this) {
            case WEST: return -1;
            case EAST: return 1;
            default: return 0;
        }
    }

    public int getDY() {
        switch(this) {
            case NORTH: return -1;
            case SOUTH: return 1;
            default: return 0;
        }
    }
}

或者,(IMO) 更好,将它们表示为一个字段

public enum Direction {
  NORTH(0,-1),
  SOUTH(0,1),
  EAST(1,0),
  WEST(-1,0);

  private int dx;
  private int dy;

  private Direction(int dx, int dy) {
    this.dx = dx;
    this.dy = dy;
  }

  public int getDX() {
    return dx;
  }

  public int getDY() {
    return dy;
  }
}

从那里我们可以直接在 Foo.bar() 中简单地使用这些方法,不需要任何逻辑:

public class Foo {

   public void bar(Direction d) {

       //can directly use d.getDX() and d.getDY()

   }

}

你关于函数调用的问题是一样的,如果删除了一级。我们可以直接将开关添加到枚举中:

public enum Type {
    VALUE_ONE, VALUE_TWO, ...

    public void callFunc() {
        switch(this) {
            case VALUE_ONE:
                callFunctionOne();
                return;
             case VALUE_TWO:
                callFunctionTwo();
                return;
             //....
        }
    }
}

然后通过直接引用该函数来使用它:

Type t = //....
t.callFunc();

您甚至可以使用一些 java-8 的东西将要调用的函数表示为一个字段

@FunctionalInterface
public interface Unit {
    public void apply();
}

public enum Type {
    VALUE_ONE(Foo::baz),
    VALUE_TWO(Foo::baz2),
    //...

    private Unit funcToCall;

    private Type(Unit u) {
        this.funcToCall = u;
    }

    public void callFunc() {
        funcToCall.apply();
    }
}        

如果type不是一个枚举,你可以使用上面的一些(但不是全部)选项。你仍然可以将你的开关逻辑集中到一个助手 method/class 中并将控制权交给它而不是 copy/pasting。 type 越应该代表某种东西,并且您发现自己越想 switch 超过它,枚举就越有可能是正确的选择。