转载

【Java奇思妙想】反射与泛型工厂

引子

反射是个近乎无敌的工具。

但使用过度会遭人嫌弃。

按CodeReview的话说,叫

“同学,你这有点过度设计啊”。

翻译过来叫

“整NM这么复杂有毛病啊。”

当你开心写完代码但得到这样的评价是会有点小尴尬。但我们站在读代码的角度,看到一串复杂玩意,确实很烦的。

然而身为一个geek,即使可能遭人嫌弃,我们也要继续去作死!

【Java奇思妙想】反射与泛型工厂

原则

但作死也要讲究原则,否则就真死了。为了不被CodeReview的大佬嫌弃,在使用反射进行花式操作时,遵循下面几个原则。

  1. 有使用价值的原则
  2. 简洁易懂的原则
  3. 可修改可替换原则

【Java奇思妙想】反射与泛型工厂

不被嫌弃的第一步就是尽量不做无用功。如果使用反射技巧能让你的代码在使用上有下列优点的话,就值得去做。

  1. 更简洁
  2. 可读性更强
  3. 减少重复代码
  4. 扩展性更强
  5. 更容易维护

第二点是维持简洁。整个类最好很短,功能单一。这样后人接手你的代码时,心理压力会小很多。

第三点也和后期维护有关,当别人需要增加功能时,而又不想用反射了,你需要提供可修改的途径。比如使用接口,比如用protected代替private。

第三点和面向对象经典的开闭原则是相违背的,放在IDEA里,你用protected它就会提醒你改成private,很烦。但完美的开闭有时过于理想了,完美开闭的前提是已有良好的类开闭设计,但在你所在的公司,可能接口变得比业务还多。为了让后人能够增加功能而不是直接copy,你需要提供一定的开放。

【Java奇思妙想】反射与泛型工厂

例子

这里给出个当时业务中我遇到的例子,就很适合用反射去解决。

先简单提下反射的特性,你可以这样去理解:

代码字面上包含的类信息,就是反射能获取的信息。

了解这点后,你就能判断何时能用反射去秀操作了。

我遇到的需求是这样:

我们有个泛型接口,我们叫它Runner<T,R>它接收一个T返回一个R,这个T和R都可以被序列化成字符串。

我们的需求分为两步,

第一步,外部发来Runner的名字,然后从所有实现类找到需要的Runner。

第二步,用这个Runner去接收外部发来的字符串,将他反序列化为T后,然后返回R序列化后的字符串。

用户给个Runner name->实例化的Runner

所以重点就是,我们需要知道T和R的类型。

但是 Java的泛型是会类型擦除的,在运行时并不知道是T还是R

一个简单想法就是,把T和R放在实现类中,比如直接用个

//简单方案,直接增加2个方法
interface Runner<T,R>{
    ...
    Type GetInputType();
    Type GetOuputType();
}

但这样每个实现类都要自己去实现这两个方法了,会有一堆多余的代码,而且得一个一个手动复制粘贴。还要注意修改时保持一致性。很麻烦不是吗。

所以这时候就可以用反射了。

我们回想一下,反射是所有代码字面上包含的类信息,比如下面这个Runner, 它明明白白的写了string和string,所以我们是能通过反射拿到那两个string的

public class StringStringRunner implements Runner<String,String> {
    @Override
    public String execute(String s) {
        return s+"!";
    }
}

换句话说,只有有了那个类名,我就能知道它的T和R。

剩下就是反射的具体实现了,这一部分纯粹是基本功我就不多说了。边写边google就是,我自己写的时候也不是都记得的,每次查或者copy以前的代码。

关键你只需要知道这里能用反射,然后发挥你的想象力,再靠你的基本功去实现它。

/**
 * 泛型类型工厂
 * <br> 这个工厂在产生泛型实例的同时,会给出该实例对应泛型接口的参数类型。
 */
public class RunnerFactory implements AbstRunnerFactory {
    /**
     * 工厂的生产单元
     */
    StringMapRunner stringMapRunner = new StringMapRunner();
    StringStringRunner stringStringRunner = new StringStringRunner();
    MapStringRunner mapStringRunner = new MapStringRunner();

    public Result create(String name) throws Exception {
        for (Field field : this.getClass().getDeclaredFields()) {
            if(field.getName().equals(name)){
                for (Type anInterface : field.getType().getGenericInterfaces()) {
                    ParameterizedType anInterface1 = (ParameterizedType) anInterface;
                    if(anInterface1.getRawType().equals(Runner.class)){

                        Type[] types = anInterface1.getActualTypeArguments();
                        return new Result(
                                types[0],
                                types[1],
                                (Runner)field.getType().newInstance()
                        );
                    }
                }
            }
        }
        throw new Exception("NoFoundThisMember "+name);
    }
    @Data
    @AllArgsConstructor
    public static class Result{
        Type inputType;
        Type outputType;
        Runner runner;
    }
}

使用起来也很方便,是我自己比较满意的一个方案。

public static void main(String[] args) throws Exception {
        //创建Runner工厂
        RunnerFactory runnerFactory = new RunnerFactory();

        //从工厂创建mapStringRunner实例
        Result result = runnerFactory.create("mapStringRunner");
        Runner runner = result.runner;
        //输出 java.util.Map<java.lang.String, java.lang.String>
        System.out.println(result.inputType);
        //输出 class java.lang.String
        System.out.println(result.outputType);

        //将字符串转换成Runner实例需要的Object并执行
        String inputJson = "{/"name/":/"Tom/"}";
        Object input = JSONObject.parseObject(inputJson, result.inputType);
        Object res = runner.execute(input);

        //输出 String
        System.out.println(res.getClass().getSimpleName());
        //输出 {name=Tom}
        System.out.println(res);
    }

但最后,我们团队还是选择了方案1,就是GetInpuType()那套方案。。。

【Java奇思妙想】反射与泛型工厂

原文  https://segmentfault.com/a/1190000022630950
正文到此结束
Loading...