一次代码重构的思考及探索
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 函数; 最后,我们还需要支持默认字段排序。但我不想在方法上增加一个参数。怎么办?可以在类里定义一个静态变量,反射获取该变量。
重构的代码如下。现在,我们不需要定义额外的东西了,而且根据任意对象类型的任意字段的任意方向排序
以上是关于一次代码重构的思考及探索的主要内容,如果未能解决你的问题,请参考以下文章