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

    修改 jar 包 或 war 包内容 有一个 java web 项目,是 .jar 或 .war 文件,我想替换其中的部分样式(.css)或功能(.class). 步骤就是解压,替换,重新打包. 以 ...

  2. 【Mysql】SpringBoot阿里Druid数据源连接池配置

    一.pom.xml添加 <!-- 配置数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> &l ...

  3. C#LeetCode刷题之#520-检测大写字母(Detect Capital)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3947 访问. 给定一个单词,你需要判断单词的大写使用是否正确. ...

  4. 调试备忘录-J-Link RTT的使用(原理 + 教程 + 应用 + 代码)

    MCU:STM32F407VE MDK:5.29 IAR:8.32 目录--点击可快速直达 目录 写在前面 什么是RTT? RTT的工作原理 RTT的性能 快速使用教程 高级使用教程 附上测试代码 2 ...

  5. 太厉害了,阿里大牛居然把Git,GitHub总结的这么全面,撸源码去

    “版本控制系统”( Version Control System, vcs)是程序代码管理软件的通称,是用来保存程序文件的修改记录以及历史版本,以便日后查看或是使用.Vcs已经有数十年的发展历史,最早 ...

  6. 企业项目实战 .Net Core + Vue/Angular 分库分表日志系统 | 控制反转搭配简单业务

    教程预览 01 | 前言 02 | 简单的分库分表设计 03 | 控制反转搭配简单业务 说明 我们上一节已经成功通过 连接提供程序存储库,获取到了 连接提供程序,但是连接提供程序和数据库连接依赖太深, ...

  7. 网络基础之IP地址

    一.IP地址 1.IP地址就是给互联网上每一台主机 (或路由器)每一个接口分配一个在全世界范围内是唯一的32位二进制的地址标识符.现在由互联网名字和数字分配机构ICANN进行分配. 2.转换成十进制 ...

  8. Nordic 52840-Timer定时器学习问题(一)

    今天在ble_app_blinky例程中移植定时器驱动,在编译过程中报出了两个错误,在此记录一下. 1. 在nRF_Dreivers中添加nrfx_timer.c文件 选中“nRF_Dreivers  ...

  9. 一张图带你玩转docker

  10. DHCPV6 vs DHCPV4

    原文链接:https://blog.csdn.net/kdb_viewer/article/details/83310904 一.DHCPv4 vs DHCPv6 1. 相同点 使用DHCP clie ...