前言
软件开发方法学的泰斗肯特·贝克(Kent Beck)曾说过:
| 我不是一个伟大的程序员,我只是一个具有良好习惯的优秀程序员。
养成良好的习惯,尤其是不断重构的习惯,是每一个优秀程序员都应该具备的素质。重构(Refactoring)就是在不改变软件现有功能的基础上,通过调整程序的结构、提高程序的质量、优化程序的性能……使其程序的设计模式和架构更趋合理,从而提高软件的稳定性、扩展性和维护性。
一、 一个需要重构的方法
1. 需求描述
需要把一个线串(一组经纬度坐标串),按照指定分段长度数组进行按比例划分。(由于指定线串的长度较小,可以近似地认为在几何平面上,无需进行球面距离换算。)
2. 代码实现
/**
* 几何辅助类
*/
public final class GeometryHelper {
/** 常量相关 */
/** 小数位数 */
private static final int DIGIT_SCALE = 8;
/** 放大比例 */
private static final double ZOOM_SCALE = 10000000000L;
/** 几何工厂 */
private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory(new PrecisionModel(PrecisionModel.FLOATING));
/**
* 构造方法
*/
private GeometryHelper() {
throw new UnsupportedOperationException();
}
/**
* 划分线串
*
* @param lineString 原始线串
* @param segmentLengthes 分段长度数组
* @return 线串数组
*/
public static LineString[] splitLineString(LineString lineString, double[] segmentLengthes) {
// 检查分段数量
if (Objects.isNull(segmentLengthes) || segmentLengthes.length < 1) {
return new LineString[] {lineString};
}
// 计算总共长度
double totalLength = Arrays.stream(segmentLengthes)
.map(segmentLength -> Math.max(segmentLength, 0.0D))
.sum();
// 计算目标长度
double lineLength = lineString.getLength();
long[] targetLengthes = Arrays.stream(segmentLengthes)
.mapToLong(segmentLength -> getTargetLength(lineLength, totalLength, segmentLength))
.toArray();
// 初始化参数值
int index = 1;
Coordinate[] coordinates = lineString.getCoordinates();
Coordinate coordinate = coordinates[0];
int length = targetLengthes.length;
LineString[] lineStrings = new LineString[length];
// 添加前面N段
for (int i = 0; i < length - 1; i++) {
// 添加线串坐标
long addupLength = 0L;
List coordinateList = new ArrayList<>();
coordinateList.add(coordinate);
for (; index < coordinates.length; index++) {
// 计算分段长度
long segmentLength = Math.round(coordinate.distance(coordinates[index]) * ZOOM_SCALE);
// 根据长度处理
boolean isBreak = true;
int compareResult = Long.compare(addupLength + segmentLength, targetLengthes[i]);
// 根据长度处理: 未达目标长度
if (compareResult < 0) {
addupLength += segmentLength;
coordinate = coordinates[index];
coordinateList.add(coordinate);
isBreak = false;
}
// 根据长度处理: 超过目标长度
else if (compareResult > 0) {
long deltaLength = targetLengthes[i] - addupLength;
coordinate = buildMiddleCoordinate(coordinate, coordinates[index], segmentLength, deltaLength);
}
// 根据长度处理: 等于目标长度
else {
index++;
coordinate = coordinates[index];
}
// 是否跳出循环
if (isBreak) {
break;
}
}
coordinateList.add(coordinate);
// 设置线串对象
lineStrings[i] = buildLineString(coordinateList);
}
// 添加最后一段
lineStrings[length - 1] = buildLineString(coordinates, index, coordinate);
// 返回线串数组
return lineStrings;
}
/**
* 构建线串
*
* @param coordinates 坐标数组
* @param index 当前序号
* @param coordinate 当前坐标
* @return 线串
*/
private static LineString buildLineString(Coordinate[] coordinates, int index, Coordinate coordinate) {
List coordinateList = new ArrayList<>();
coordinateList.add(coordinate);
coordinateList.addAll(Arrays.asList(ArrayUtils.subarray(coordinates, index, coordinates.length)));
return buildLineString(coordinateList);
}
/**
* 构建线串
*
* @param coordinateList 坐标列表
* @return 线串
*/
private static LineString buildLineString(List coordinateList) {
return GEOMETRY_FACTORY.createLineString(coordinateList.toArray(new Coordinate[0]));
}
/**
* 构建中间坐标
*
* @param coordinate1 坐标1
* @param coordinate2 坐标2
* @param segmentLength 分段长度
* @param deltaLength 增量长度
* @return 中间坐标
*/
private static Coordinate buildMiddleCoordinate(Coordinate coordinate1, Coordinate coordinate2,
long segmentLength, long deltaLength) {
double deltaScale = deltaLength * 1.0D / segmentLength;
double middleX = round(coordinate1.x + (coordinate2.x - coordinate1.x) * deltaScale, DIGIT_SCALE);
double middleY = round(coordinate1.y + (coordinate2.y - coordinate1.y) * deltaScale, DIGIT_SCALE);
return new Coordinate(middleX, middleY);
}
/**
* 获取目标长度
*
* @param lineLength 线路长度
* @param totalLength 总共长度
* @param segmentLength 段长度
* @return 目标长度
*/
private static long getTargetLength(double lineLength, double totalLength, double segmentLength) {
return Math.round(Math.max(segmentLength, 0.0D) * ZOOM_SCALE * lineLength / totalLength);
}
/**
* 四舍五入
*
* @param value 双精度浮点值
* @param scale 保留小数位数
* @return 四舍五入值
*/
private static double round(double value, int scale) {
return BigDecimal.valueOf(value).setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
}
3. 备注说明
在超过目标长度时,获取了一个中间坐标,由于数据精度的关系,这个坐标可能跟上一坐标或下一坐标重合。这里为了降低这块逻辑的复杂度,没有进行前后坐标的去重处理。
4. 存在问题
在方法splitLineString(划分线串)中,存在一个两层循环,导致了方法逻辑复杂、层级较深、代码量大。如果把外层循环体提炼为一个方法,就能够使代码更简洁、更清晰、更容易维护。
接下篇:https://developer.aliyun.com/article/1228185?groupCode=java