在开发过程中,程序提供的功能由简单变得复杂,承担功能的主要类也会因此变得庞大臃肿,如果不加以维护,就会散发出浓重的代码味道。下面这篇博文,主要讲述了利用Enum,反射等手段简化重构代码的过程。

代码涉及的工程是一个基于Webhook调用的项目,Webhook可以简单理解为网络上文件和文件夹的创生和监控API,用户可以通过调用API在目标机器上创建文件目录,当它们发生变化时获得提醒。

既然是调用API,以某账户创建webhook为例,返回的可能性就只有三种:目标机器收到指令创建成功、目标机器收到指令但因为webhook已经存在而不做操作、API调用出现异常,再以该用户删除webhook为例,返回的可能性也是三种:收到指令发现要删除的对象存在删除成功,收到指令发现要删除的对象不存在而不做操作,API调用异常...归纳一下,返回的可能性就是目标已改变,目标未改变,调用失败三种情况。这时我们就可用一个Enum对象作为wenhook基本操作函数的返回值:

public enum CmdResultType {
CHANGED(1), UNCHANGE(0), FAILED(-1); private static final Logger logger = LoggerFactory.getLogger(CmdResultType.class); private int index; private CmdResultType(int index) {
this.index = index;
} public static CmdResultType fromIndex(int idx) {
for (CmdResultType type : CmdResultType.values()) {
if (type.getIndex() == idx) {
return type;
}
} logger.warn("Unexcepted index:{} was set to CmdResultType", idx);
return null;
} public int getIndex() {
return index;
} public void setIndex(int index) {
this.index = index;
}
}

该类一开头就定义三种可能的返回值,并且提供了一个静态函数以方便从int值得到CmdResultType类型,下面就能看到这个类的应用:

public CmdResultType delete(String accountName) {
logger.info(FUNCTION_ACCOUNT_NAME, "WebhookService.delete()", accountName); try {
int deletedCount = 0;
List<String> webhookIdList = getWebhookIdList(this.boxApiConn);
for (String webhookId : webhookIdList) {
logger.info("Found Webhood(id={})", webhookId); if (deleteWebHook(this.boxApiConn, webhookId)) {
deletedCount++;
}
} logger.info("Deleted webhook count:{}", deletedCount); return CmdResultType.fromIndex(deletedCount); } catch (Exception ex) {
logger.warn("Cannot delete webhook due to {}", ex.getMessage());
} return CmdResultType.FAILED;
}

上面这个函数中,由删除数量而产生返回的CmdResultType类型,如果删除数量为零,说明目标未改变,自然会返回UNCHANGE;如果删除数量为一,说明目标已改变,就会返回CHANGED;这两种情况之外,自然是返回FAILED调用失败。因为业务约定,一个账户下只允许拥有一个Webhook,因此删除数量最大就是1,不会有大于一的情况。

有了CmdResultType这个类,delete函数和delete函数的调用者之间就有了一个契约,callee和caller相当于在CmdResultType类里做好了约定。

下面我们可以看看某个caller的调用情况:

 CmdResultType resultType = null;

 // Execute command via reflection
try {
Class<?> serviceCls = WebhookService.class;
Method method = serviceCls.getMethod(cmdBundle.childCmd, String.class);
method.setAccessible(true);
resultType = (CmdResultType) method.invoke(service, accountName);
} catch (Exception ex) {
String errMsg = String.format("Can not invoke method:%s via reflection because of %s",
cmdBundle.childCmd, ex.getMessage());
logger.warn(errMsg);
throw new RtmsWebhookException(errMsg, ex);
} String text = "";
if (CmdResultType.CHANGED == resultType) {
text = cmdBundle.changedWord;
retval++;
} else if (CmdResultType.UNCHANGE == resultType) {
text = cmdBundle.unchangeWord;
} else {
text = cmdBundle.failedWord;
}

上面的第八行就是通过反射调用delete函数,而16到24行就是根据返回值做出相应处理。

使用Enum作为返回值比int好的地方在于int值作为约定是松散和缺乏约束的,caller的书写者不得不查看callee函数的代码才能准确判断返回值代表什么意思;而Enum做返回值只用到Enum类里看就好了,看常量比看代码容易得多,callee的编写者也不可能因为业务变化而弄出一个在Enum类里没有定义过的值来。

好了,调用wenhook的三个基本函数create,delete,get(listall,二者功能等同)写好了,因为业务的扩展,还派生出了三个批量调用函数createall,deleteall,getall, 而用户是通过命令方式调用的,指令是“java -jar xxx.jar create accountname”的方式,程序解析出命令后再调用具体函数。

这样就产生了六个分支,而分支多了一是可读性差,二是会导致类似的重复代码,而利用反射我们可以消除分支,达到简化代码的目的:

 printCmdBegin(cmd.getText());

 // Execute command via reflection
Class<?> serviceCls = WebhookService.class;
Method method = serviceCls.getMethod(cmd.getText(), String.class);
method.setAccessible(true);
CmdResultType result = (CmdResultType) method.invoke(service, accountName); if (CmdResultType.CHANGED == result || CmdResultType.UNCHANGE == result) {
runCmdRetval = true;
}
printCmdResult(cmd.getText(), runCmdRetval);

这段代码代表的是create、delete、get三种函数的调用,一次性可以消除三个分支。

 CmdType cmd = CmdType.fromText(action);
printCmdBegin(cmd.getText()); // Execute command via reflection
Class<?> batchServiceCls = BatchWebhookService.class;
Method method = batchServiceCls.getMethod(cmd.getText());
method.setAccessible(true);
int changed = (int) method.invoke(batchService); if (changed >= 0) {
runCmdRetval = true;
}
printCmdResult(cmd.getText(), runCmdRetval);

上面这段代码代表的是createall,deleteall,getall三种函数的调用,也消除了三个分支。

以上情况是指令的文本正好与调用函数名吻合的情况,但如果不吻合比如大小写不一致,多了前缀后缀怎么办呢?不用怕,用HashMap做个映射就好了。

至于指令本身和调用函数也得做个约定,于是CmdType类就产生了:

public enum CmdType {
CREATEALL("createall"), DELETEALL("deleteall"), CREATE("create"), LISTALL("listall"), DELETE("delete"), GET("get"), GETALL("getall"); private static final Logger logger = LoggerFactory.getLogger(CmdType.class); private String text; private CmdType(String txt) {
this.text = txt;
} public static CmdType fromText(String txt) {
for (CmdType type : CmdType.values()) {
if (type.getText().equalsIgnoreCase(txt)) {
return type;
}
} logger.warn("Unexcepted text:{} was set to CmdType", txt);
return null;
} public String getText() {
return text;
} public void setText(String text) {
this.text = text;
}
}

有了这个类,程序能接受什么指令一目了然,这比去翻设计文档明了多了。

外界文本型的指令通过校验后,会转化成CmdType型的格式:

CmdType cmd = CmdType.fromText(action);

程序再根据cmd的值进行反射调用,就不会因为输入错误命令而导致反射调用异常的情况发生了。

CmdType相当于在用户输入的命令和实际运行的函数间做了个契约,这边是Enum的用意之一。

好了,关于Enum作为callee和caller之间的约定,反射用来消除分支和重复代码就讲到这里。

--2020年4月18日--

使用枚举类Enum作为callee和caller的约定,运用反射消除分支和重复代码在命令式程序中的应用的更多相关文章

  1. Java枚举类enum

    枚举类enum是JDK1.5引入的,之前都是用public static final int enum_value来代替枚举类的.枚举类enum是一种特殊的类,它默认继承了类java.lang.Enu ...

  2. Java基础(七)泛型数组列表ArrayList与枚举类Enum

    一.泛型数组列表ArrayList 1.在Java中,ArrayList类可以解决运行时动态更改数组的问题.ArrayList使用起来有点像数组,但是在添加或删除元素时,具有自动调节数组容量的功能,而 ...

  3. java 枚举类 enum 总结

    枚举定义: enum是计算机编程语言中的一种数据类型.枚举类型:在实际问题中,有些变量的取值被限定在一个有限的范围内.例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等.如果把这些量 ...

  4. java 枚举 类 enum

    public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializab ...

  5. 枚举类 enum,结构体类 struct

    1.枚举类型的值,直观易于理解,见词知意. 格式: enum 枚举类名:值类型 { 值1, 值2, 值n } 每个值默认(省略“:值类型”)以int型数据存储,从0开始. 使用格式:枚举类名 变量=枚 ...

  6. java枚举类Enum方法简介(valueof,value,ordinal)

    Enum作为Sun全新引进的一个关键字,看起来很象是特殊的class,   它也可以有自己的变量,可以定义自己的方法,可以实现一个或者多个接口.   当我们在声明一个enum类型时,我们应该注意到en ...

  7. 枚举类enum的values()方法

    value()方法可以将枚举类转变为一个枚举类型的数组,因为枚举中没有下标,我们没有办法通过下标来快速找到需要的枚举类,这时候,转变为数组之后,我们就可以通过数组的下标,来找到我们需要的枚举类.接下来 ...

  8. zend framework获取数据库中枚举类enum的数据并将其转换成数组

    在model中建立这种模型,在当中写入获取枚举类的方法 请勿盗版,转载请加上出处http://blog.csdn.net/yanlintao1 class Student extends Zend_D ...

  9. 枚举类enum应用以及注解@transient应用

    1.增加枚举类 public enum RightTypeEnum { AUTHORITY("访问权限") private String type; RightTypeEnum(S ...

随机推荐

  1. MySQL数据库的约束

    一 默认值约束 约束语句 default  ‘默认值’ 在建立表的时候在想要加默认约束的字段名,数据类型后面加default ‘默认值’ 例如 : create table emp( uid  int ...

  2. Java中访问控制修饰符的详解和示例——Java学习

    Java中的四个访问控制修饰符 简述 在Java中共有四个: public -- 对外部完全可见 protected -- 对本包和所有子类可见 默认(不需要修饰符)-- 对本包可见 private ...

  3. 转圈游戏C++

    转圈游戏 问题描述 n 个小伙伴(编号从 0 到 n-1)围坐一圈玩游戏.按照顺时针方向给 n 个位置编号,从0 到 n-1.最初,第 0 号小伙伴在第 0 号位置,第 1 号小伙伴在第 1 号位置, ...

  4. MySQL最全存储引擎、索引使用及SQL优化的实践

    1 MySQL的体系结构概述 整个MySQL Server由以下组成 :Connection Pool :连接池组件Management Services & Utilities :管理服务和 ...

  5. OpenStack虚拟机virtaulinterfance 网络设备在libvirt的代码梳理

    nova创建虚机网卡实际设备的代码调用流程为 _create_domain_and_network---->plug_vifs-->LibvirtGenericVIFDriver.plug ...

  6. 【转】Windows10删除文件时却提示文件不存在的解决方案

    Windows10系统使用一段时间后用户都会定期进行删除清理系统垃圾,减少系统盘的容量占用,但在删除的过程中许多用户都有可能遇到无法删除的情况,如下为删除文件时却提示文件不存在的解决方案. 1.新建一 ...

  7. latex:公式的上下标

    1.行内公式的上下标 在行间公式中,例如\[\max_{i}\]的排版结果是 而在行内公式中,$max_{i}$的排版结果为 ,如果要使其仍在正下方,可插入字体尺寸档次命令 $\displaystyl ...

  8. Qt QDialog添加最大化和最小化按钮

    Qt QDialog添加最大化和最小化按钮(转载) QDialog窗体右上角默认是没有最小化和最大化按钮的. 1.效果 2.上代码 1 // 设置窗体最大化和最小化 2 Qt::WindowFlags ...

  9. win环境下安装配置openCV-4.3.0

    win环境下安装openCV-4.3.0 首先下载 推荐国内镜像 官网太太太慢了 附上 下载地址 下载之后打开exe解压到目录都是常规操作 环境变量的配置 依次打开到系统变量的path 新建一个路径为 ...

  10. e3mall商城总结13之订单确认(有BUG)

    说在前面的话 上一节说了购物车的生成,本节主要说了在购物车的列表上去结算,从而生成一个未支付的订单,生成的订单默认状态为1, 题目说的BUG是因为所有数据都是通过前端向后端生成的,包括订单的金额.因此 ...