在大的if / else构造中调用函数/类的好方法

时间:2016-07-12 13:21:05

标签: java if-statement coding-style switch-statement

我在大多数旧的(主要是java)项目中看到过很多代码部分,看起来像

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

其中“type”可以是枚举或真正的任何东西,整个事情也可以用开关/案例风格编写。 我的问题是:有没有“更好”(更时尚/更短/更可读)的方式来实现这一目标? 我在PHP中看过如下构造:

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

但我不确定这是否真的是“更好”的方式,如果它甚至可能用于其他语言。

2 个答案:

答案 0 :(得分:1)

更有趣的问题imo是你想要实现的目标?

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

abstract class Type{
     abstract void method();
}

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

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

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)

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

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

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

如果两种解决方案都不适合您,则可以选择switch语句或if-else级联。

答案 1 :(得分:1)

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

如果所涉及的代码块只发生一次,那么没有反射,你就无法做得更好。你必须在某处实现逻辑,可能涉及相同的if-else / switch块。但是,如果你发现自己在多个地方复制粘贴相同的if-elseif-elseif-elseif ....块,你可以做得更好。

如果type是一个枚举,你可以将逻辑移动到枚举本身,从OO的角度来看这非常好。请考虑以下事项:

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

将这个块复制粘贴到项目周围显然是一个坏主意。如果您添加了新方向,则必须返回到每个此类块以添加正确的添加。从OO的角度来看,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不是枚举,则可以使用上述选项中的一些(但不是全部)。您仍然可以将开关逻辑转换为辅助方法/类,并将控制权传递给它而不是复制/粘贴。 type应该代表的东西越多,你发现自己想要switch越多,就越有可能选择枚举。