文章目录
- 优化案例
- 初次优化
- 再次优化
- 看看Spring源码的处理
优化案例
假设一个场景,
开发代码时,需要对类中的方法进行遍历,判断有没有注解@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的这个代码类,让我觉得尤其惊艳的点,就是对方法行为的封装,MethodCallback 和 MethodFilter。
许多优秀设计中抽象不单单针对是对具象实体,像这种对无法被归成某一类的抽象,让代码更优雅,更值得反复揣摩。
正如【HeadFirst 设计模式】开篇中的,【组合优先于继承】章节,对duck实体的飞行方式,鸣叫属性的抽象,有异曲同工之妙。