【问题标题】:Good way of calling Functions/class in big if/else constructs在大 if/else 结构中调用函数/类的好方法
【发布时间】:2016-07-12 13:21:04
【问题描述】:

我在大多数旧(主要是 java)项目中看到了许多看起来像

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

其中“类型”可以是一个枚举或任何东西,整个事情也可以用 switch/case 样式编写。 我的问题是:是否有“更好”(更时尚/更短/更易读)的方式来实现这一目标? 我在 PHP 中见过这样的结构:

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

但我不确定这是否真的是“更好”的方式,甚至在其他语言中是否可行。

【问题讨论】:

  • 好吧,更好的方法是使用 case 语句,但不……这是 php 的一大优点,您可以在构建代码时编写代码并修改其编写方式。 . 我不知道如何在 java 中做到这一点。
  • 我的猜测是 PHP 最终在其构造背后使用了某种 switch 语句。
  • 您可以使用反射来动态调用您的方法检查此 SO 以了解有关 Java 方法反射的更多详细信息:stackoverflow.com/questions/3050967/…
  • @SohaibFarooqi 这就是我搜索的内容,但正如答案所说:这“应该是最后的手段”,所以不完全是我所说的“更好”

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


【解决方案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)

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

所以回答这个问题本身(至少我是怎么理解的):

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

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

【讨论】:

  • 如果我的 callFunctionOne() 是构造函数而不是函数调用,这会以任何方式工作吗?喜欢“new ObjectOne()”吗?
  • @ahe 我不太明白你的问题。你的意思是如何使用 oo 方法?还是要在对象实例化时调用方法?
  • 使用多态性而不是切换方法是更好的方法,但不会摆脱检查类型参数。有几个概念和模式可以解决这个问题。您可以创建一个接收 Type 参数然后返回正确对象的工厂。 (工厂也必须对所有类型进行 switch/if-else)
  • 根据您的具体应用,您可以使用the factory method patternthe strategy patternthe command pattern,这只是众多可能性中的一小部分。
【解决方案2】:

正如评论中所指出的,有一些方法可以使用 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 越多,枚举就越有可能是正确的选择。

【讨论】:

  • 我非常喜欢 java-8 方法!我认为这实际上与您在脚本语言中所做的最接近!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-20
相关资源
最近更新 更多