• 重构代码——简单工厂模式+模板方法模式


    有个项目里有好几套产品规则,需要根据每个产品计算利息分账和生成还款计划。项目里原先的代码尝试去封装这个变化,每个产品规则创建了一个对应的类。为了方便理解,这里画了一个类图:

    将代码设计成这样是一个好的趋势,符合面向对象的思想,既能复用公共的逻辑,又更好去维护。但是,有一处代码我觉得可以优化下,具体代码大致如下:

    // 其他代码
    ...
    
    ParamService paramService;
    if (ProductTypeEnum.Axx.getValue() == proType) {
        paramService = this.axxParamService;
    } else if (ProductTypeEnum.Bxx.getValue() == proType) {
        paramService = this.bxxParamService;
    } else if (ProductTypeEnum.Cxx.getValue() == proType) {
        paramService = this.cxxParamService;
    } else if (ProductTypeEnum.Dxx.getValue() == proType) {
        paramService = this.dxxParamService;
    } else if (ProductTypeEnum.Exx.getValue() == proType) {
        paramService = this.exxParamService;
    } else if (ProductTypeEnum.Fxx.getValue() == proType) {
        paramService = this.fxxParamService;
    } else {
        logger.error("invalid proType:{}", proType);
        throw new BusinessExcepton("invalid proType:" + proType);
    }
    
    // 其他代码
    ...
    

    简单工厂模式

    倒不是说这段代码写得有多么糟糕,而是项目中有好几处都这样去使用,让代码不仅重复,而且也让业务代码更难阅读,不易维护。我们应该要提取重复的代码,并编写能够表达语义的代码。这里,比较适合的一个套路就是“简单工厂模式”,通过“简单工厂模式”将new对象的代码从业务代码中抽离出去,保持代码的“开闭原则”。上面的代码不是直接new的,而是通过Spring注入,不过本质上是对象的创建工作,不应该与业务代码耦合在一起。重构的做法就是将上面创建对象实例的代码封装到一个工厂类,由工厂类负责创建对于的对象实例。

    @Component
    public class ParamServiceFactory {
        // 成员变量
        ...
        
        public ParamService createParamService(int proType) {
            switch(proType) {
                case ProductTypeEnum.Axx.getValue(): this.axxParamService;
                case ProductTypeEnum.Bxx.getValue(): this.bxxParamService;
                case ProductTypeEnum.Cxx.getValue(): this.cxxParamService;
                case ProductTypeEnum.Dxx.getValue(): this.dxxParamService;
                case ProductTypeEnum.Exx.getValue(): this.exxParamService;
                case ProductTypeEnum.Fxx.getValue(): this.fxxParamService;
                default: 
                    logger.error("invalid proType:{}", proType);
                    throw new BusinessExcepton("invalid proType:" + proType);
            }
        }
    }
    

    在需要ParamService对象实例时,传入一个适当的proType参数给ParamServiceFactory的createParamService(int proType)方法就行了。如果需要增加产品规则,只需要改动ParamServiceFactory这个类,而不像之前那要需要改动好几个地方。并且,将原来业务代码中的近20行代码,缩减为1行,让我们更容易阅读业务代码。

    模板方法的应用

    前面是重构了创建对象的代码,将创建产品规则的类对象代码从业务代码中分离抽取,一方面降低了业务代码与产品规则代码的耦合度,方便之后调整产品规则;另一方面,降低了业务代码的复杂度,便于后期维护。不过,这个项目还有值得推敲的地方,原来的开发针对每个产品规则,创建了一个具体ParamService,这本来是个不错的想法,但是具体的方法实现十分混乱,让刚开始接触这段代码的我,阅读代码十分难受。

    这里涉及的业务逻辑并不复杂,开头说过,就是计算利息分账和生成还款计划。生成还款计划没什么好说的,每个产品都有自己的一套还款计划规则,因此,父类ParamService生成还款计划的方法是一个抽象方法是没有问题的。但是,计算利息分账里面包含了计算免息券、判断是否批扣等逻辑,这些逻辑每个产品都是相同的。在实现代码时就应该将这些逻辑放到父类中去,以便子类继承并使用。如果公共逻辑都放在子类中,不仅让子类方法变得臃肿,而且也没有真正享受到继承的好处,反而只是徒增了几个实现类,没有丝毫的好处。

    这里我们完全可以使用模板方法模式,把计算免息券、判断是否批扣等逻辑在父类实现,而计算利息分账的逻辑在子类实现。这样做最大程度地复用了代码,并且公共的逻辑只在一个地方,所以容易修改。

    使用模板方法后的类图如下:

    可以与文章开头未修改的类图比较下,发现父类ParamService多了几个方法,正是这几个方法把之前重复的代码给分离出去了。这里最关键的方法是getInterestSplit()方法,先看下代码:

    public final double getInterestSplit(RepayRequest repayRequest) {
        double reduce = getReduction(repayRequest);
        boolean isBatch = isBatch(repayRequest);
        return calculateInterestSplit(repayRequest, reduce, isBatch);
    }
    

    getInterestSplit()方法被定义为final,这是为了防止子类去修改这个方法。getReduction()和isBatch()在父类实现,calculateInterestSplit由具体的子类实现。

    经过这样修改,原本混乱的代码逻辑,一下子清晰多了,并且维护起来也方便。

  • 相关阅读:
    记计账需求分析
    进度条07
    Runner站立会议03
    Runner站立会议02
    Runner站立会议01
    构建之法阅读笔记03
    团队成员介绍
    团队进展报告(1)
    今日事——Sprint计划会议
    团队开发——软件需求分析报告
  • 原文地址:https://www.cnblogs.com/bluemilk/p/10330537.html
Copyright © 2020-2023  润新知