这样写代码太优雅了吧

优化案例

假设一个场景,

开发代码时,需要对类中的方法进行遍历判断有没有注解@NotNull,暂时没有合适的工具类,需要自己手搓一个。


无须多想,分分钟秒了,我们写出如下的代码很简单,妥妥的满足需求。

    public Boolean hashAnnotation(Class<?> clazz) {
        //为了案例,此处不使用clazz.isAnnotationPresent(annotationClass)
        for (Annotation annotation : clazz.getAnnotations()) {
            if (annotation.annotationType().equals(NotNull.class)) {
                return true;
            }
        }
        return false;
    }

然而,事情不会那么简单,后续发现,还需要判断有没有@NotBlank注解。
这会儿不能再傻傻的单写个判断@NotBlank的方法,于是写下的方法支持所有注解的判断,看上去代码有着那么点拓展性。
在这里插入图片描述

    public Boolean hashAnnotation(Class<?> clazz, Class<? extends Annotation> annotationClass) {
        //为了案例,此处不使用clazz.isAnnotationPresent(annotationClass)
        for (Annotation annotation : clazz.getAnnotations()) {
            if (annotation.annotationType().equals(annotationClass)) {
                return true;
            }
        }
        return false;
    }

没有什么代码能一蹴而就,意外不期而遇,开发过程中又需要获取类中的所有只有一个参数的方法,和之前获取注解的方法也没法进行复用,只能重新再写一个了。

    public List<Method> isMethodWithSingleParameterOfType(Class<?> clazz) {
        List<Method> methods = new ArrayList<>();
        for (Method method : clazz.getMethods()) {
            if (method.getParameterCount() == 1) {
                methods.add(method);
            }
        }
        return methods;
    }

初次优化

假如后面开发中,还需要对类内部的方法进行处理,难道每次都要依葫芦画瓢写一个新方法吗?
且不说代码的量在不断膨胀,我们经常挂在口上的可拓展性也几乎没有,后续任何的变动,都有可能影响原有的逻辑,除非无脑写新方法。

作为一个合格的Coder,我们要努力的消灭代码中的坏味道。

简单分析上面的几个案例,寻找其中的共性,分离出其中可变化的部分。

需求都需要遍历类中的所有方法

可变的无非是对方法的操作,比如判断方法注解、获取方法的参数等

依据设计原则,封装不变部分拓展可变部分;
不变的部分撑起函数的骨架,再抽象可变部分,稍微一变动,写出如下的代码。增加接口MethodCallback

    public void doWithMethods(Class<?> clazz, MethodCallback mc) {
        Method[] methods = clazz.getMethods();
        /**
         * 封装不变
         */
        for (Method method : methods) {
            mc.doWith(method);
        }
    }

    /**
     * 抽象可变部分
     */
    @FunctionalInterface
    public interface MethodCallback{

        void doWith(Method method);
    }

将可变的方法操作,封装为接口,一者大大提高了方法的复用性,二者是抽象了操作之后对函数的操作也解耦了,不得不说很优雅。

这样优化之后,我们上述的案例都可以改写如下(只是简单展示,并不完善),如果后续有别的个性化的类似需求,只需稳坐钓鱼台,以不变应万变。

再次优化

如果上述案例中再多一个可变的部分,比如说遍历所有方法名称为XXX的注解,
怎么办呢?

依据前面的设计,再次抽象,将过滤再次独立隔离,当做可变部分,增加接口MethodFilter

方法骨架内增加固定逻辑,在方法执行之前,先过滤下方法matches(),支持调用方使用任何的过滤逻辑。


    public void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) {
        Method[] methods = clazz.getMethods();
        /**
         * 封装不变
         */
        for (Method method : methods) {
            if (!mf.matches(method)) {
                continue;
            }
            mc.doWith(method);
        }
    }

    /**
     * 抽象可变部分
     */
    @FunctionalInterface
    public interface MethodFilter {

        boolean matches(Method method);
    }

看看Spring源码的处理

ReflectionUtils

package org.springframework.util  #ReflectionUtils

骨架方法
在这里插入图片描述

抽象的接口方法
在这里插入图片描述


上述案例即来自Spring的这个代码类,让我觉得尤其惊艳的点,就是对方法行为的封装,MethodCallbackMethodFilter

许多优秀设计中抽象不单单针对是对具象实体,像这种对无法被归成某一类的抽象,让代码更优雅,更值得反复揣摩。
在这里插入图片描述

正如【HeadFirst 设计模式】开篇中的,【组合优先于继承】章节,对duck实体的飞行方式,鸣叫属性的抽象,有异曲同工之妙。

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值