Java超大函数代码重构案例分析——使用反射、函数式接口Function、建造者模式Builder重构

项目中某一个函数被代码检查工具扫出来137行,属于超大函数(大于50行的函数),经过两次重构达到自己认为的理想状态,使用到了反射,函数式接口,以及建造者模式来完成。下面案例介绍,涉及项目代码,部分简化,仅供给大家参考思想,如有更好的重构方法,欢迎留言。

public class SctpParaEntity extends Model<SctpParaEntity> {
    /**
     * 表ID
     */
    public static final int TABLE_ID = 379;

    private static final long serialVersionUID = 6407871887365507698L;

    private Integer clusterId;

    private int maxIpNum;

    private int minRtoValue;

    private int maxRtoValue;

    private int initTtoValue;

    private int alphaRtoValue;

    private int betaRtoValue;

    private int hearteatInterval;

    private int maxAssocRetTimes;

    private int maxPathRetTimes;

    private int noCongestLevel;

    private int lowCongestLevel;

    private int highCongestLevel;

    private int chkSendSum;

    private int chkRecvSum;

    private int chkSumType;

    private int cookieValidTime;

    private int maxInitRetTimes;

    private int inStreamNum;

    private int outStreamNum;

    private int dispatchPacketFlag;

    private int sctpBundFlag;

    private int extendSupportFlag;

    private int protocolLayerType;

    private int ackPolicy;

    private int dataNum;

    private int delayDuration;

    private int sendAck;

    private int lifeTimeFlag;

    private int ciycleNum;

    /**
     * SCTP参数转换成界面显示的list
     *
     * @return the list
     */
    public List<SctpParaDto> eto() {
        List<SctpParaDto> tpLst = new ArrayList<>();
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "maxIpNum",
                        String.valueOf(maxIpNum)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "minRtoValue",
                        String.valueOf(minRtoValue)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "maxRtoValue",
                        String.valueOf(maxRtoValue)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "initTtoValue",
                        String.valueOf(initTtoValue)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "alphaRtoValue",
                        String.valueOf(alphaRtoValue)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "betaRtoValue",
                        String.valueOf(betaRtoValue)));

        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "hearteatInterval",
                        String.valueOf(hearteatInterval)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "maxAssocRetTimes",
                        String.valueOf(maxAssocRetTimes)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "maxPathRetTimes",
                        String.valueOf(maxPathRetTimes)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "noCongestLevel",
                        String.valueOf(noCongestLevel)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "lowCongestLevel",
                        String.valueOf(lowCongestLevel)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "highCongestLevel",
                        String.valueOf(highCongestLevel)));

        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "chkSendSum",
                        EtoMessage.changeFlagToStr(chkSendSum)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "chkRecvSum",
                        EtoMessage.changeFlagToStr(chkRecvSum)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "chkSumType",
                        EtoMessage.changeSumTypeToStr(chkSumType)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "cookieValidTime",
                        String.valueOf(cookieValidTime)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "maxInitRetTimes",
                        String.valueOf(maxInitRetTimes)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "inStreamNum",
                        String.valueOf(inStreamNum)));

        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "outStreamNum",
                        String.valueOf(outStreamNum)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "dispatchPacketFlag",
                        EtoMessage.changeFlagToStr(dispatchPacketFlag)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "sctpBundFlag",
                        EtoMessage.changeFlagToStr(sctpBundFlag)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "extendSupportFlag",
                        EtoMessage.changeFlagToStr(extendSupportFlag)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "protocolLayerType",
                        EtoMessage.changeLevelTypeToStr(protocolLayerType)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "ackPolicy",
                        EtoMessage.changeAckPolicyToStr(ackPolicy)));

        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "dataNum",
                        String.valueOf(dataNum)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "delayDuration",
                        String.valueOf(delayDuration)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "sendAck",
                        String.valueOf(sendAck)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "lifeTimeFlag",
                        EtoMessage.changeFlagToStr(lifeTimeFlag)));
        tpLst.add(
                new SctpParaDto(
                        CurrentRequest.getAppName(),
                        CurrentRequest.getClusterName(),
                        "ciycleNum",
                        String.valueOf(ciycleNum)));

        return tpLst;
    }
}
public class SctpParaDto {
    private String appName; // 项目类型
    private String clusterName; // 集群名称
    private Integer clusterId; // 集群ID

    private String paramName; // 参数名

    private String paramValue; // 参数值

    /**
     * 无参构造函数
     */
    public SctpParaDto() {}

    /**
     * 有参构造函数
     *
     * @param appName 项目名称
     * @param clusterName 集群名称
     * @param paramName 参数名称
     * @param paramValue 参数值
     */
    public SctpParaDto(String appName, String clusterName, String paramName, String paramValue) {
        this.appName = appName;
        this.clusterName = clusterName;
        this.clusterId = CurrentRequest.getClusterId();
        this.paramName = paramName;
        this.paramValue = paramValue;
    }
}

这里有两个类,SctpParaEntity和SctpParaDto,一个用于数据库插入,一个用于页面展示,需要将entity转换为dtoList。这里存在的问题,eto这个方法名太随意,不能准确表达方法意思,同时由于entity字段较多,需要将其中29个字段转换,直接new对象,虽然很好理解,但是不够优雅,造成超大函数,进行了第一次重构。

@Data
@Builder
public class SctpParaDto {
    private String appName; // 项目类型
    private String clusterName; // 集群名称
    private Integer clusterId; // 集群ID

    @NotBlank
    @StrEnumCheck(enumClass = SctpParaEnum.class, message = "{InvalidSctpParameters.message}")
    @Pattern(regexp = "^[a-zA-Z0-9]+$")
    private String paramName; // 参数名

    @NotBlank
    @Pattern(regexp = "^[a-zA-Z0-9_]+$")
    private String paramValue; // 参数值

    /**
     * 无参构造函数
     */
    @Tolerate
    public SctpParaDto() {}
}
public List<SctpParaDto> convertToSctpParaDtoList() {
        List<SctpParaDto> list = new ArrayList<>(29); // 初始化29个
        Field[] fields = this.getClass().getDeclaredFields();
        List<String> flagToStrList = Arrays.asList("chkSendSum", "chkRecvSum", "dispatchPacketFlag", "sctpBundFlag",
            "extendSupportFlag", "lifeTimeFlag");
        SctpParaDto.SctpParaDtoBuilder builder = SctpParaDto.builder()
            .appName(CurrentRequest.getAppName())
            .clusterName(CurrentRequest.getClusterName())
            .clusterId(CurrentRequest.getClusterId());
        for (int i = 3; i < fields.length; i++) { // 从第4个fieLd开始反射
            String fieldName = fields[i].getName();
            Object fieldValue = ReflectUtils.invokeGet(this, fieldName);
            builder.paramName(fieldName);
            if (flagToStrList.contains(fieldName)) {
                builder.paramValue(EtoMessage.changeFlagToStr((int) fieldValue));
            } else if (Objects.equals(fieldName, "ackPolicy")) {
                builder.paramValue(EtoMessage.changeAckPolicyToStr((int) fieldValue));
            } else if (Objects.equals(fieldName, "protocolLayerType")) {
                builder.paramValue(EtoMessage.changeLevelTypeToStr((int) fieldValue));
            } else if (Objects.equals(fieldName, "chkSumType")) {
                builder.paramValue(EtoMessage.changeSumTypeToStr((int) fieldValue));
            } else {
                builder.paramValue(String.valueOf(fieldValue)).build();
            }
            list.add(builder.build());
        }
        return list;
    }

将Dto增加@Builder注解,使用建造者模式,删除原有的构造方法,无参构造需要增加@Tolerate注解,否则会报错。Entity中的eto方法名改为convertToSctpParaDtoList,同时使用反射和建造者模式创建dto对象,大大简化了代码,但是还是存在问题。convertToSctpParaDtoList方法依旧在entity这个类中,造成类的职责过多,同时if else分支过多,不利于扩展,于是进行第二次重构。

public class SctpParaAssembler {
    private static final Map<String, Function<Integer, String>> SCTP_PARA_PROCESS_MAP;

    static {
        SCTP_PARA_PROCESS_MAP = new HashMap<>();

        List<String> stringList = Arrays.asList("maxIpNum", "minRtoValue", "maxRtoValue", "initTtoValue",
            "alphaRtoValue", "betaRtoValue", "hearteatInterval", "maxAssocRetTimes", "maxPathRetTimes",
            "noCongestLevel", "lowCongestLevel", "highCongestLevel", "cookieValidTime", "maxInitRetTimes",
            "inStreamNum", "outStreamNum", "dataNum", "delayDuration", "sendAck", "ciycleNum");
        stringList.forEach(str -> SCTP_PARA_PROCESS_MAP.put(str, String::valueOf));

        List<String> flagToStrList = Arrays.asList("chkSendSum", "chkRecvSum", "dispatchPacketFlag", "sctpBundFlag",
            "extendSupportFlag", "lifeTimeFlag");
        flagToStrList.forEach(str -> SCTP_PARA_PROCESS_MAP.put(str, EtoMessage::changeFlagToStr));

        SCTP_PARA_PROCESS_MAP.put("ackPolicy", EtoMessage::changeAckPolicyToStr);
        SCTP_PARA_PROCESS_MAP.put("protocolLayerType", EtoMessage::changeLevelTypeToStr);
        SCTP_PARA_PROCESS_MAP.put("chkSumType", EtoMessage::changeSumTypeToStr);
    }

    /**
     * Convert to sctp para dto list list
     *
     * @param entity entity
     * @return the list
     */
    public static List<SctpParaDto> convertToSctpParaDtoList(SctpParaEntity entity) {
        List<SctpParaDto> list = new ArrayList<>(29); // 初始化29个
        SctpParaDto.SctpParaDtoBuilder builder = SctpParaDto.builder()
            .appName(CurrentRequest.getAppName())
            .clusterName(CurrentRequest.getClusterName())
            .clusterId(CurrentRequest.getClusterId());

        Field[] fields = entity.getClass().getDeclaredFields();
        for (int i = 3; i < fields.length; i++) { // 从第4个fieLd开始反射
            String fieldName = fields[i].getName();
            Integer fieldValue = (Integer) ReflectUtils.invokeGet(entity, fieldName);
            list.add(builder.paramName(fieldName)
                .paramValue(SCTP_PARA_PROCESS_MAP.get(fieldName).apply(fieldValue))
                .build());
        }

        return list;
    }
}

将上述方法单独抽取出来放到SctpParaAssembler 中,使用Function接口对每个字段处理,Map去获取Function,消除了if else,利于扩展,同时让类的职责更加明确。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* * 原始需求背景: * 网宿CDN要按月收取客户的服务费用,根据流量的大小、 * 服务的类型等,收取不同的费用,收费规则如下: * web应用:1000元/M * 流媒体应用:1000元/M*0.7 * 下载应用:1000元/M*0.5 * 月末打印报表时,要罗列每个用户每个频道的费用、客户总费用, * 还要打印该客户的重要性指数,重要性指数=网页流/100+下载流量/600; * * 需求变更场景: * 系统已经开发出来了,接下来,运维部门现在希望对系统做一点修改, * 首先,他们希望能够输出xml,这样可以被其它系统读取和处理,但是, * 这段代码根本不可能在输出xml的代码中复用report()的任何行为,唯一 * 可以做的就是重写一个xmlReport(),大量重复report()中的行为,当然, * 现在这个修改还不费劲,拷贝一份report()直接修改就是了。 * 不久,成本中心又要求修改计费规则,于是我们必须同时修改xmlReport() * 和report(),并确保其一致性,当后续还要修改的时候,复制-黏贴的问题就 * 浮现出来了,这造成了潜在的威胁。 * 再后来,客服部门希望修改服务类型和用户重要性指数的计算规则, * 但还没决定怎么改,他们设想了几种方案,这些方案会影响用户的计费规则, * 程序必须再次同时修改xmlReport()和report(),随着各种规则变得越来越复杂, * 适当的修改点越 来越难找,不犯错误的机会越来越少。 * 现在,我们运用所学的OO原则和方法开始进行改写吧。 */

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值