使用枚举类Enum作为callee和caller的约定,运用反射消除分支和重复代码在命令式程序中的应用
在开发过程中,程序提供的功能由简单变得复杂,承担功能的主要类也会因此变得庞大臃肿,如果不加以维护,就会散发出浓重的代码味道。下面这篇博文,主要讲述了利用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的约定,运用反射消除分支和重复代码在命令式程序中的应用的更多相关文章
- Java枚举类enum
枚举类enum是JDK1.5引入的,之前都是用public static final int enum_value来代替枚举类的.枚举类enum是一种特殊的类,它默认继承了类java.lang.Enu ...
- Java基础(七)泛型数组列表ArrayList与枚举类Enum
一.泛型数组列表ArrayList 1.在Java中,ArrayList类可以解决运行时动态更改数组的问题.ArrayList使用起来有点像数组,但是在添加或删除元素时,具有自动调节数组容量的功能,而 ...
- java 枚举类 enum 总结
枚举定义: enum是计算机编程语言中的一种数据类型.枚举类型:在实际问题中,有些变量的取值被限定在一个有限的范围内.例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等.如果把这些量 ...
- java 枚举 类 enum
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializab ...
- 枚举类 enum,结构体类 struct
1.枚举类型的值,直观易于理解,见词知意. 格式: enum 枚举类名:值类型 { 值1, 值2, 值n } 每个值默认(省略“:值类型”)以int型数据存储,从0开始. 使用格式:枚举类名 变量=枚 ...
- java枚举类Enum方法简介(valueof,value,ordinal)
Enum作为Sun全新引进的一个关键字,看起来很象是特殊的class, 它也可以有自己的变量,可以定义自己的方法,可以实现一个或者多个接口. 当我们在声明一个enum类型时,我们应该注意到en ...
- 枚举类enum的values()方法
value()方法可以将枚举类转变为一个枚举类型的数组,因为枚举中没有下标,我们没有办法通过下标来快速找到需要的枚举类,这时候,转变为数组之后,我们就可以通过数组的下标,来找到我们需要的枚举类.接下来 ...
- zend framework获取数据库中枚举类enum的数据并将其转换成数组
在model中建立这种模型,在当中写入获取枚举类的方法 请勿盗版,转载请加上出处http://blog.csdn.net/yanlintao1 class Student extends Zend_D ...
- 枚举类enum应用以及注解@transient应用
1.增加枚举类 public enum RightTypeEnum { AUTHORITY("访问权限") private String type; RightTypeEnum(S ...
随机推荐
- 前端面试 vue 部分 (3)——v-show和v-if的区别
v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景: v-show 则适用于需要非常频繁切换条件的场景. v-if 是条件渲染,如果在初始渲染时条件为假,则什么也不做--直到条件第一次变为 ...
- 漫画 | 到底是什么让IT人如此苦逼???
写在最后 漫画是有点夸张,不过多少还是有点现实开发过程的影子! 老板很乐观,核心就是三个月上线,至于怎么办那是底下人的事. 产品很无奈,心里盘算着,万万不可在他这一环节耽误了进度,时间这么赶,先出个壳 ...
- speedtest测速网站测速节点添加流程
一.准备一台服务器: 系统需求:常见Linux系统: 二.服务器入网(确保可以访问互联网): 三.ssh登录到服务器安装speedtest守护程序程序包: 安装和启动,执行以下命令: curl -O ...
- unity探索者之微信分享回调
版权声明:本文为原创文章,转载请声明http://www.cnblogs.com/unityExplorer/p/7574561.html 上一遍讲了微信分享的一些坑,然后就忘了回调这事儿了,今天补上 ...
- 编译原理根据项目集规范族构造LR(0)分析表
转载于https://blog.csdn.net/Johan_Joe_King/article/details/79058597?utm_medium=distribute.pc_relevant.n ...
- Linux文件权限-笔记
文件权限共10个字符,第一个字符表示该文件是[文件夹]或[文件]——如果是字符“d"则表示该文件是文件夹:如果是字符“-”则表示是文件. 后九个字符,三个一组,共三组,分别表示[所有者权限] ...
- Kafka API实战
第4章 KafkaAPI实战 1)启动zk和kafka集群,在kafka集群中打开一个消费者 [hadoop102 kafka]$ bin/kafka-console-consumer.sh \ -- ...
- WordCount of Software Engineering
1.Github项目地址:https://github.com/BayardM/WordCount 2.PSP表格(before): PSP2.1 Personal Software Process ...
- 【转】在Python的struct模块中进行数据格式转换的方法
这篇文章主要介绍了在Python的struct模块中进行数据格式转换的方法,文中还给出了C语言和Python语言的数据类型比较,需要的朋友可以参考下 Python是一门非常简洁的语言,对于数据类型的表 ...
- react+antd的todolist开发
使用localStorage缓存在cookie里刷新不会充重置 参考链接 create-react-app入门教程https://www.jianshu.com/p/77bf3944b0f4 http ...