• 使用枚举类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的调用情况:

     1 CmdResultType resultType = null;
     2 
     3 // Execute command via reflection
     4 try {
     5     Class<?> serviceCls = WebhookService.class;
     6     Method method = serviceCls.getMethod(cmdBundle.childCmd, String.class);
     7     method.setAccessible(true);
     8     resultType = (CmdResultType) method.invoke(service, accountName);
     9 } catch (Exception ex) {
    10     String errMsg = String.format("Can not invoke method:%s via reflection because of %s",
    11             cmdBundle.childCmd, ex.getMessage());
    12     logger.warn(errMsg);
    13     throw new RtmsWebhookException(errMsg, ex);
    14 }
    15 
    16 String text = "";
    17 if (CmdResultType.CHANGED == resultType) {
    18     text = cmdBundle.changedWord;
    19     retval++;
    20 } else if (CmdResultType.UNCHANGE == resultType) {
    21     text = cmdBundle.unchangeWord;
    22 } else {
    23     text = cmdBundle.failedWord;
    24 }

    上面的第八行就是通过反射调用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”的方式,程序解析出命令后再调用具体函数。

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

     1 printCmdBegin(cmd.getText());
     2 
     3 // Execute command via reflection
     4 Class<?> serviceCls = WebhookService.class;
     5 Method method = serviceCls.getMethod(cmd.getText(), String.class);
     6 method.setAccessible(true);
     7 CmdResultType result = (CmdResultType) method.invoke(service, accountName);
     8 
     9 if (CmdResultType.CHANGED == result || CmdResultType.UNCHANGE == result) {
    10     runCmdRetval = true;
    11 }
    12 printCmdResult(cmd.getText(), runCmdRetval);

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

     1 CmdType cmd = CmdType.fromText(action);
     2 printCmdBegin(cmd.getText());
     3 
     4 // Execute command via reflection
     5 Class<?> batchServiceCls = BatchWebhookService.class;
     6 Method method = batchServiceCls.getMethod(cmd.getText());
     7 method.setAccessible(true);
     8 int changed = (int) method.invoke(batchService);
     9 
    10 if (changed >= 0) {
    11     runCmdRetval = true;
    12 }
    13 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日--

  • 相关阅读:
    英文网站优化之十个非常不错的Zen Cart插件
    kindeditor4.0.4个性化修改
    C#线程从陌生到熟悉(1)
    c# 关于LISTBOX的添加项的问题 以及不重复插入
    CSS设置字体为楷体
    SqlServer2005安装成功后补加Sa用户
    c# winform 用子窗体刷新父窗体,子窗体改变父窗体控件的值
    实例讲解如何把表格变量传递到存储过程中
    StringBuilder
    初学基于.net三层架构的ERP系统(1)
  • 原文地址:https://www.cnblogs.com/heyang78/p/12723933.html
Copyright © 2020-2023  润新知