在开发过程中,程序提供的功能由简单变得复杂,承担功能的主要类也会因此变得庞大臃肿,如果不加以维护,就会散发出浓重的代码味道。下面这篇博文,主要讲述了利用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. GitLab 系列文章

    GitLab 系列文章 记录 GitLab 的相关文章 列表 Docker 搭建 GitLab GitLab CI/CD 配置 GitLab 配置模板 访问 GitLab 数据库 GitLab 转让所 ...

  2. java运算符二

    一 赋值运算符 /* * 赋值运算符 * +=, -=, *=, /=, %= : * 上面的运算符作用:将等号左右两边计算,会将结果自动强转成等号左边的数据类型,再赋值给等号左边的 * 注意:赋值运 ...

  3. 2020-07-16:如何获得一个链表的倒数第n个元素?

    福哥答案2020-07-16: 1.快慢指针.快指针先走n步,然后快慢指针同时走,直到快指针走到尾.2.两次遍历.第一次遍历获取链表长度,然后计算出序号,然后遍历获取序号下的元素.3.数组保存.遍历一 ...

  4. Vue 如何优雅的根据条件动态显示组件

    常规情况下,在里动态加载不同组件的方式为: <template> <!-- 符合条件A,加载组件A --> <BusinessComponentA v-if=" ...

  5. CSS动画实例:一颗躁动的心

    在页面中放置一个类名为container的层作为盛放心心的容器,在该层中再定义一个名为heart的子层,HTML代码描述如下: <div class="container"& ...

  6. Vue学习(十三)模版引擎算是预处理器吗?

    前言 今天在看vue-loader预处理器配置相关的内容,突然看到了Pug,然后有了一个疑问:模版引擎原来是预处理器吗? 答案是:YES 说明 这里重点讨论使用不同的js模板引擎作为预处理器, 下面示 ...

  7. 笔记:Ubuntu安装LAMP环境

    一.更换Ubuntu的镜像源 镜像源路径:/etc/apt/sources.list 备份:cp /etc/apt/sources.list /etc/apt/souces.list.bak 使用阿里 ...

  8. java十进制二进制互转

    1. 十进制转二进制 原理:给定的数循环除以2,直到商为0或者1为止.将每一步除的结果的余数记录下来,然后反过来就得到相应的二进制了. 比如8转二进制,第一次除以2等于4(余数0),第二次除以2等于2 ...

  9. DP搬运工1 [来自yyy--mengbier的预设型dp]

    DP搬运工1 题目描述 给你 \(n,K\) ,求有多少个 \(1\) 到 \(n\) 的排列,满足相邻两个数的 \(max\) 的和不超过 \(K\). 输入格式 一行两个整数 \(n,K\). 输 ...

  10. 修改vsftpd的默认根目录/var/ftp/pub到另一个目录

    修改ftp的根目录只要修改/etc/vsftpd/vsftpd.conf文件即可: 加入如下几行: local_root=/var/www/html chroot_local_user=YES ano ...