一次代码重构的思考及探索

Posted 编程大观园

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一次代码重构的思考及探索相关的知识,希望对你有一定的参考价值。

分离和组合关注点。

引子

如下代码所示:


@Setter
@Getter
public class ConnBriefInfo {
    private Long visitCount;
    private String firstTime;
    private String lastTime;

    public static Comparator<? super ConnBriefInfo> getComparator(Sort sort) {
        if (sort == null) {
            return (o1, o2) -> (StringUtils.compare(o2.getFirstTime(), o1.getFirstTime()));
        }

        if (sort.iterator().hasNext()) {
            Sort.Order order = sort.iterator().next();
            if ("firstTime".equals(order.getProperty())) {
                if (order.getDirection().isAscending()) {
                    return (o1, o2) -> (StringUtils.compare(o1.getFirstTime(), o2.getFirstTime()));
                } else {
                    return (o1, o2) -> (StringUtils.compare(o2.getFirstTime(), o1.getFirstTime()));
                }
            }

            if ("lastTime".equals(order.getProperty())) {
                if (order.getDirection().isAscending()) {
                    return (o1, o2) -> (StringUtils.compare(o1.getLastTime(), o2.getLastTime()));
                } else {
                    return (o1, o2) -> (StringUtils.compare(o2.getLastTime(), o1.getLastTime()));
                }
            }

            if ("visitCount".equals(order.getProperty())) {
                if (order.getDirection().isAscending()) {
                    return Comparator.comparingLong(ConnBriefInfo::getVisitCount);
                } else {
                    return (o1, o2) -> (Long.compare(o2.getVisitCount(), o1.getVisitCount()));
                }
            }
        }

        return (o1, o2) -> (StringUtils.compare(o2.getFirstTime(), o1.getFirstTime()));
    }
}

displayMd5s = connBriefInfos.stream()
                    .sorted(ConnBriefInfo.getComparator(param.toPageable().getSort()))
                    .skip((param.getPage() - 1) * param.getSize())
                    .limit(param.getSize())
                    .collect(Collectors.toList());
@Getter
@Setter
public class ConnectionList {

    private long visitCount;
    private String firstTime;
    private String lastTime;

    public static Comparator<? super ConnectionList> getComparator(Sort sort) {
        if (sort == null) {
            return (o1, o2) -> (StringUtils.compare(o2.getFirstTime(), o1.getFirstTime()));
        }

        if (sort.iterator().hasNext()) {
            Sort.Order order = sort.iterator().next();
            if ("firstTime".equals(order.getProperty())) {
                if (order.getDirection().isAscending()) {
                    return (o1, o2) -> (StringUtils.compare(o1.getFirstTime(), o2.getFirstTime()));
                } else {
                    return (o1, o2) -> (StringUtils.compare(o2.getFirstTime(), o1.getFirstTime()));
                }
            }

            if ("lastTime".equals(order.getProperty())) {
                if (order.getDirection().isAscending()) {
                    return (o1, o2) -> (StringUtils.compare(o1.getLastTime(), o2.getLastTime()));
                } else {
                    return (o1, o2) -> (StringUtils.compare(o2.getLastTime(), o1.getLastTime()));
                }
            }

            if ("visitCount".equals(order.getProperty())) {
                if (order.getDirection().isAscending()) {
                    return Comparator.comparingLong(ConnectionList::getVisitCount);
                } else {
                    return (o1, o2) -> (Long.compare(o2.getVisitCount(), o1.getVisitCount()));
                }
            }
        }

        return (o1, o2) -> (StringUtils.compare(o2.getFirstTime(), o1.getFirstTime()));
    }

}

connectionList.stream()
        .sorted(ConnectionList.getComparator(param.toPageable().getSort()))
        .collect(Collectors.toList());

两段 getComparator 有一些明显重复的代码。 看上去应该可以消减这种重复,不过仔细一看,似乎还不那么容易。

这里有三点差异:

  • 根据指定字段比较;
  • 根据指定方向排序;
  • 返回指定对象类型的比较器。

看上去是三个不同维度的用来排序的组合。怎么才能把这三个维度分离开呢?


使用反射进行优化

第一个自然的想法,是使用反射的方式,将字段的获取通用化。

如下所示: 可以说比之前整洁多了,这样的代码对于生产环境,已经足够好了。只是,返回的对象类型仍然有点受限,且处理的类型也有点受限。有没有办法做得更好呢?


使用函数式编程

要按照指定字段排序,除了使用反射,还可以使用函数式编程。 函数式编程的前身就是函数指针。

  • 用字段获取函数来表达获取对象指定字段的语义,用枚举来获取字段对应的字段获取函数;
  • 比较对象实现比较字段的接口;

如下所示:


/**
 * @Description 获取对象比较器
 *
 * 差异点:
 * 1. 比较使用的字段不同,依据指定字段
 * 2. 比较的顺序不同;
 * 3. 返回的比较器对象不同;
 */

public class ComparatorGenerator {

    public static Comparator<? extends ComparatorObject> getComparator(Function<ComparatorObject,Object> compFunc, boolean isAscending) {
        return isAscending ? (o1, o2) -> (StringUtils.compare(String.valueOf(compFunc.apply(o1)), String.valueOf(compFunc.apply(o2)))) :
                                     (o1, o2) -> (StringUtils.compare(String.valueOf(compFunc.apply(o2)), String.valueOf(compFunc.apply(o1))));
    }

    public static Comparator<? extends ComparatorObject> getComparator(Sort sort) {

        if (sort == null) {
            return getComparator(ComparatorFuncEnum.getCompFunc("firstTime"), true);
        }

        if (sort.iterator().hasNext()) {
            Sort.Order order = sort.iterator().next();
            Function<ComparatorObject, Object> compFunc = ComparatorFuncEnum.getCompFunc(order.getProperty());
            return getComparator(compFunc, order.getDirection().isAscending());
        }

        return getComparator(ComparatorFuncEnum.getCompFunc("firstTime"), true);

    }


    @Getter

    enum ComparatorFuncEnum {

        firstTime("firstTime", ComparatorObject::getFirstTime),
        lastTime("lastTime", ComparatorObject::getLastTime),
        visitCount("visitCount", ComparatorObject::getVisitCount),

        ;

        private String field;
        Function<ComparatorObject, Object> compFunc;

        ComparatorFuncEnum(String field, Function<ComparatorObject, Object> compFunc) {
            this.field = field;
            this.compFunc = compFunc;
        }

        public static Function<ComparatorObject, Object> getCompFunc(String field) {
            for (ComparatorFuncEnum cfe: ComparatorFuncEnum.values()) {
                if (cfe.getField().equals(field)) {
                    return cfe.compFunc;
                }
            }
            return null;
        }
    }

}

public interface ComparatorObject {
    Long getVisitCount();
    String getFirstTime();
    String getLastTime();
}
 
public class ConnBriefInfo implements ComparatorObject {
    // ...
}

单测如下:




public class ComparatorGeneratorTest {

    @Test

    public void testGetComparator() {

        List<ConnBriefInfo> connBriefInfoList = Arrays.asList(
              new ConnBriefInfo(3L, "2021-06-11", "2021-05-12"),
                new ConnBriefInfo(5L, "2021-06-19", "2021-05-19"),
                new ConnBriefInfo(2L, "2021-06-15", "2021-05-15")
        );

        List<ConnBriefInfo> sortedByFirstTime = connBriefInfoList.stream()
                .sorted((Comparator<? super ConnBriefInfo>) ComparatorGenerator.getComparator(null, "firstTime"))
                .collect(Collectors.toList());

        System.out.println(sortedByFirstTime);

        List<ConnBriefInfo> sortedByVisitorCount = connBriefInfoList.stream()
                .sorted((Comparator<? super ConnBriefInfo>) ComparatorGenerator.getComparator(new Sort("visitCount")))
                .collect(Collectors.toList());

        System.out.println(sortedByVisitorCount);

        List<ConnBriefInfo> sortedByVisitorCountDesc = connBriefInfoList.stream()
                .sorted((Comparator<? super ConnBriefInfo>) ComparatorGenerator.getComparator(new Sort(Sort.Direction.DESC,"visitCount")))
                .collect(Collectors.toList());

        System.out.println(sortedByVisitorCountDesc);


        List<ConnBriefInfo> sortedByLastTime = connBriefInfoList.stream()
                .sorted((Comparator<? super ConnBriefInfo>) ComparatorGenerator.getComparator(new Sort(Sort.Direction.DESC,"lastTime")))
                .collect(Collectors.toList());

        System.out.println(sortedByLastTime);

    }

}



不过,这种方式仍然有限制:受限于指定的字段枚举。如果我需要使用其它对象的其它字段比较,就要定义新的接口及新的字段枚举;而且需要不安全的强制类型转换。

显然,这样并不够好。如何摆脱这种限制呢?


使用泛型来解除类型限制

前面的 getComparator 使用了 ComparatorObject 对象来表达要返回的可比较的对象类型。实际上,可以使用泛型来表达,解除类型限制。

稍作改动,定义字段获取函数 getComparator(Function<T, String> compFunc, boolean isAscending) ,这样就可以应对各种排序要求了。

不过,我们必须能够支持 Sort 传参,这样就需要把指定字段名转换为 Function<T, String> compFunc, 就有 convert 函数; 最后,我们还需要支持默认字段排序。但我不想在方法上增加一个参数。怎么办?可以在类里定义一个静态变量,反射获取该变量。

重构的代码如下。现在,我们不需要定义额外的东西了,而且根据任意对象类型的任意字段的任意方向排序

以上是关于一次代码重构的思考及探索的主要内容,如果未能解决你的问题,请参考以下文章

如何重构这个 Java 代码片段

关于后台部分业务重构的思考及实践

python 用于数据探索的Python代码片段(例如,在数据科学项目中)

如何重构"箭头型"代码

APP迁移

APP架子迁移指南