Java中如何优雅地调用多个方法

问题背景

有某个颇为复杂的功能,功能拆分时把该功能拆分成了数十个步骤,每个步骤用一个方法来实现。需要依次调用数十个方法/函数,这些方法有相同的签名。
为了后期的维护和扩展,显然不可能像这样去调用:

	step1(); // 第一步
	step2(); // 第二步
	...
	stepn(); // 第n步

这样去调用的话,如果后期要在每个方法/函数后面都增加一个额外的功能(比如测量每个步骤的运行时间),那么工作量就翻了N倍,超出想象!

如果把这些方法直接或间接放入一个数组中,遍历这个数组,取出这些方法调用,代码就会变得非常简洁,也容易维护。如:

   stepList = [step1, step, ..., stepn]; // 把这些步骤对应的方法都放入一个数组中
   for(int i=0; i<n; i++) { // 遍历
   		stepList[i](); // 调用
   }

围绕这个思路,我们来看看在具体编程语言中如何实现。这里以Java为例。当然,如果用 C、Javascript、PHP,会更简单。

各个步骤的示例代码如下:
为测试,在一个类中编写了3个方法,来代表需要执行的多个步骤。

public class TestAction {
    /**
     * 步骤一
     */
    public boolean step1() {
        System.out.println("step 1");
        return false;
    }

    /**
     * 步骤2
     */
    public boolean step2() {
        System.out.println("step 2");
        return true;
    }

    /**
     * 步骤3
     */
    public boolean step3() {
        System.out.println("step 3");
        return true;
    }
}

实现1. 反射 (Reflection)

把方法名字以字符串形式存放在一个数组,然后通过反射(Reflection)来获取到这个方法,再用invoke调用该方法。

/**
* 方式1: 用反射批量调用方法 (该方法写在 TestAction 类中)
* @return
 */
public boolean doAllByReflect() {
    Class claz = getClass();
    String stepList[] = {
        "step1", "step2", "step3" // 把方法名称放在数组里
    };

    boolean result = false;
    try {
        for(String methodName : stepList) {
            Method method =claz.getMethod(methodName, null);  // 通过反射获取到该方法
            result = (boolean)method.invoke(this, null); // 调用
        }
    } catch (Exception e) { // 为缩减篇幅,具体异常的捕获和处理就不写了
    	e.printStackTrace();
   }
   return result;
}

测试输出
step 1
step 2
step 3

评价

优点:

  1. 如果需要追加步骤或者调整步骤顺序,只需要更改存储方法名称的数组就行了,简单方便。
  2. 如果需要在每个方法调用前/后增加处理逻辑,在for循环里面处理一次就行,工作量也不大。

缺点:
1.上面纯粹使用反射,把方法名字以字符串形式放入数组,编辑器和编译器无法发现拼写错误,只有在运行时才能发现错误。所以编译器会提示需要捕获4种异常:

  • NoSuchMethodException 没有该方法。属于拼写错误导致的
  • IllegalArgumentException 参数非法错误。调用时传入了错误类型/数量的参数时导致的
  • InvocationTargetException 调用的方法出现异常。
  • IllegalAccessException 非法访问异常。如果在同一个类内调用,不存在该问题。

所以如果代码编写不注意,容易埋下隐患。

实现2. 反射 + 注解 (Refelction + Annotation)

比之于通过方法的字符串名称来获取方法实例,可以给需要调用的方法用注解(Annotation)打上标签,然后用反射获取所有方法(getDeclaredMethods())并遍历,判断是否有该注解(isAnnotationPresent()方法),有就调用。

实现如下:

2.1 定义Action注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface Action {
    int sort(); // sort用来给方法排序,让方法按指定顺序调用,这里不考虑
}
2.2 把需要调用的方法加上Action注解
public class TestAction {
    /**
     * 步骤一
     */
    @Action(sort=1)
    public boolean step1() {
        System.out.println("step 1");
        return false;
    }

    /**
     * 步骤2
     */
    @Action(sort=2)
    public boolean step2() {
        System.out.println("step 2");
        return true;
    }

    /**
     * 步骤3
     */
    @Action(sort=3)
    public boolean step3() {
        System.out.println("step 3");
        return true;
    }
}
2.3 调用
/**
 * 方式2: 反射+注解 批量调用方法(该方法写在 TestAction 类中)
 * @return
 */
public boolean doAllAnnotation() {
    Class claz = getClass();

    boolean result = false;
    try {
        Method[] methodList = claz.getDeclaredMethods();
        for(Method method : methodList) {
            // 如果该方法有Aaction注解,调用之
            if (method.isAnnotationPresent(Action.class)) {
                method.invoke(this, null);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
        result = false;
    }

    return result;
}

测试输出
step 1
step 2
step 3

评价

优点:

  1. 如果需要追加步骤,增加相应的方法即可。
  2. 如果需要调整步骤顺序,调整注解中sort的值,在调用之前对其排序。
  3. 如果需要在每个方法调用前/后增加处理逻辑,在for循环里面处理一次就行,工作量也不大。

缺点:

  1. 调整顺序时需要增加额外的排序步骤。
  2. 如果注解被误用到了不是该步骤的方法上,会导致运行时出现问题。如果注解被误用到了不是该步骤的方法上,会导致运行时出现问题。

实现3. 利用Java8新特性: 方法引用和函数式接口

Java8提供了一些新特性,可以像函数式编程语言一样把方法当做一等公民,这两个便是 方法引用(Method Reference)和函数式接口(Functional Interface)。

首先利用函数式接口让每个方法都具有相同的类型(从而可以放入同一个数组),然后利用方法引用获取到该方法的引用,最后用 for循环依次调用每个方法。

实现如下:

3.1 定义一个接口 IAction
@FunctionalInterface
public interface IAction {
    boolean execute(); // 方法签名要与step系列方法的一样
}
3.2 调用
/**
 * 方式3: 方法引用 + 函数式接口 (该方法写在 TestAction类中)
 * @return
 */
public boolean doAll() {
    IAction[] stepList = {
        this::step1, // step1()方法的引用
        this::step2,
        this::step3
    };
    boolean result = false;
    for(IAction action : stepList) {
        action.execute(); // 执行该方法
    }

    return result;
}

在Java8中,还可以用stream系列方法来代替上面的for循环:

Arrays.asList(stepList).stream().forEach(step-> step.execute());

测试输出
step 1
step 2
step 3

评价

优点:

  1. 如果需要追加步骤或调整步骤顺序,修改stepList这个数组就行。
  2. 如果需要在每个方法调用前/后增加处理逻辑,在for循环里面处理一次就行,工作量也不大
  3. 与前面两个使用反射来获取到方法的引用,这里直接通过方法引用,编辑器和编译器可以检查出潜在的错误,从而安全性较高。

缺点:
暂未发现明确的缺点。

4. 其它实现方式

当然也有其它方式来实现在一个循环中依次调用多个方法 ,比如:枚举Enum,或者 把方法拆分到每个不同的类,每个类里的方法都是相同的名字和签名,然后创建每个类的实例放入一个数组,遍历,调用之。

由于这些实现方式的代码量较大,这里就不予举例了。

此文完。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值