问题背景

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

 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,或者 把方法拆分到每个不同的类,每个类里的方法都是相同的名字和签名,然后创建每个类的实例放入一个数组,遍历,调用之。

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

此文完。

Java中如何优雅地调用多个方法相关推荐

  1. java调用visa的dll库,查看新闻/公告--[备忘]Java中,使用JNA调用Visa32.dll,控制频谱仪~~...

    Java中,使用JNA调用Visa32.dll,控制频谱仪~~ C:\Program Files\Agilent\IO Libraries Suite\ 有visa.chm,是方法和属性的说明. 首先 ...

  2. java 中lock,java中lock获取锁的四种方法

    在java接口中会存放着许多方法,方便线程使用时的直接调用.对于lock接口大家都不陌生,我们已经初步对概念进行了理解.那么在获取锁的方法上想必还不是很清楚.下面我们就lock获取锁的四种方法分别进行 ...

  3. java面试题27 java中下面哪些是Object类的方法()

    java面试题27 java中下面哪些是Object类的方法() A notify() B notifyAll() C sleep() D wait() 蒙蔽树上蒙蔽果,蒙蔽树下你和我.遇到这种题,我 ...

  4. Java中如何编写一个完美的equals方法

    在Java中,由于语言规范要求equals方法具有以下特性: 1)自反性:对于任何非空引用x,x.equals(x)应当返回true. 2)对称性:对于任何引用x和y,当且仅当x.equals(y)返 ...

  5. Java中Date和Calender类的使用方法

    查看文章     Java中Date和Calender类的使用方法 2009-10-04 20:49 Date和Calendar是Java类库里提供对时间进行处理的类,由于日期在商业逻辑的应用中占据着 ...

  6. 关于Java中Match类的appendReplacement()方法的一个坑{ character to be escaped }

    关于Java中Match类的appendReplacement()方法的一个坑{java.lang.IllegalArgumentException: character to be escaped ...

  7. java 转换gbk编码,java中GBK转UTF-8乱码的解决方法

    java中GBK转UTF-8乱码的解决方法 如果自己采用的是GBK编码,对方采用得到是UTF-8编码,发送数据时需要将GBK编码数据转换成UTF-8编码数据,这样对方才不会乱码. 问题出现:GBK转U ...

  8. Java中动态加载字节码的方法 (持续补充)

    文章目录 Java中动态加载字节码的方法 1.利用 URLClassLoader 加载远程class文件 2.利用 ClassLoader#defineClass 直接加载字节码 2.1 类加载 - ...

  9. JAVA中线程同步的几种实现方法

    JAVA中线程同步的几种实现方法 一.synchronized同步的方法: 1.synchronized同步方法 即有synchronized关键字修饰的方法. 由于java的每个对象都有一个内置锁, ...

最新文章

  1. C++ 笔记(13)— 函数(函数声明、函数定义、函数调用[传值、指针、引用]、函数参数默认值、函数重载)
  2. Android 系统自动重启Bug(高通平台)
  3. ARM公布“物联网”嵌入式mbed OS系统软件平台
  4. mybatis增删改查快速实现!!!
  5. Selenium | 网上教程
  6. android 弹出菜单 toast,Android学习第二天:Toast(提醒)、Menu(菜单)、Intent的显式和隐式(包括打开、适配网站,调用拨号界面等)...
  7. C++获取PE文件的入口点
  8. Linux USB 驱动开发(二)—— USB 驱动几个重要数据结构
  9. 300plc与组态王mpi通讯_S7-300与S7-200之间的MPI通信
  10. 面试官 | 什么是递归算法?它有什么用?
  11. 百度小程序全套源码下载、免费分享,一键生成百度小程序
  12. 技术人应有的职业意识
  13. day25-静态、组合、继承
  14. iOS马甲包预审分析工具
  15. web课程设计网页规划与设计 :DW旅游主题网页设计——凤阳智慧旅游官方-地方旅游网站模板html源码HTML+CSS+JavaScript
  16. 用python构建多只股票日收益率直方图_Barra纯因子收益率的Python实现
  17. 取消UL和OL符号以及padding和margin后恢复默认值的CSS
  18. 一种TV端自动化测试应用OTA升级的方法
  19. 嵌入式linux环境取得U盘容量等信息的方法
  20. 苏宁小Biu系列产品大幅提升服务标准:亲儿子与干儿子不一样?

热门文章

  1. maya humanIk 动画pose快速镜像 插件及教程
  2. Python基础组队学习
  3. 截取string的几种方式
  4. 老板不会告诉你企业安全包括哪些事情
  5. [聊一聊系列]聊一聊移动web分辨率的那些事儿
  6. 关于ueditor表格不显示边框的两种形式
  7. webpack es6转es5
  8. java和微软不得不说的故事
  9. html中input字体加粗,更改checkboxGroupInput标签的字体标记(即粗体,斜体)
  10. 支持CSDN论坛,广告最少的论坛