在大 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
超过它,枚举就越有可能是正确的选择。
我在大多数旧项目(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
超过它,枚举就越有可能是正确的选择。