如何用最小代价重构你的构造函数

1,259 阅读5分钟

在重构老项目的过程中,我们不难发现经常会有这样的代码:构造函数很多(个人认为超过2个以上的构造函数所属的类,就有必要进行一番修改了),往往不知道如何维护,遇到版本迭代的时候,如果要修改之前的老代码则显的信心不足,只能在里面加补丁,代码习惯好点的可能加补丁的时候还会增加一点注释,代码习惯差一点的注释都没有。时间久了这部分代码就成了屎山,而且这个类也变的越来越难以维护。本文就从几个维度上教你如何重构类似的场景。

下面是一个支付结果的类,看看类似的代码风格在你的项目中有没有(如果真的没有说明你们的团队技术水平真的很好,建议点击右上角关闭本文,不要浪费时间)

//支付渠道
interface PayChannel {

}
//银行渠道
class BankChannel implements PayChannel {

}
//微信
class WxChannel implements PayChannel {

}
//支付宝
class AliPayChannel implements PayChannel {

}
/**
 * 支付结果类
 */
public class PayResult {
    //支付渠道
    private PayChannel payChannel;
    //支付时间
    private Date payDate;
    //订单总金额
    private Double totalValue;
    //实际支付金额
    private Double paymentValue;
    //用券抵消的金额
    private Double couponValue;
    //贷款支付的金额
    private Double loanValue;

    //银联支付 没有 用券的资格 也没有用贷款支付的资格
    public PayResult(Date payDate, Double totalValue, Double paymentValue) {
        this.payDate = payDate;
        this.totalValue = totalValue;
        this.paymentValue = paymentValue;

        this.payChannel = new BankChannel();
        this.loanValue = 0.0d;
        this.paymentValue = 0.0d;
    }

    //微信支付没有贷款支付的能力
    public PayResult(Date payDate, Double totalValue, Double paymentValue, Double couponValue) {
        this.payDate = payDate;
        this.totalValue = totalValue;
        this.paymentValue = paymentValue;
        this.couponValue = couponValue;
        this.payChannel = new WxChannel();
        this.loanValue = 0.0d;
    }


    public PayResult(PayChannel payChannel, Date payDate, Double totalValue, Double paymentValue, Double couponValue, Double loanValue) {
        this.payChannel = payChannel;
        this.payDate = payDate;
        this.totalValue = totalValue;
        this.paymentValue = paymentValue;
        this.couponValue = couponValue;
        this.loanValue = loanValue;
    }

}

可以看出来PayResult这个类 仅仅有六个字段,但是构造函数就达到了三个。很不好维护。而且通常在项目里,这样的基础类可能用到的地方很多很多,我们一时半会也不敢随意大改,怕引发线上故障

类似于:

public class TestMain {
    public static void main(String[] args) {
        PayResult p1 = new PayResult(new Date(), 3.1d, 3.1d);
        PayResult p2 = new PayResult(new AliPayChannel(), new Date(), 5.2d, 3.2d, 3.1d, 0.1d);
        PayResult p3 = new PayResult(new Date(), 3.1d, 2.1d, 1.0d);

    }
}

调用的地方太多,压根不敢随便大改。毕竟重构的首要条件是不要引发线上故障。那么有没有较为温和的方式能够优化一下这样的代码呢? 毕竟其实上述的构造函数里面的重复代码也挺多的,构造函数越多,重复代码的伤害就越大。碰到这种情况,我们通常会利用构造函数链接来完成重构,就是指:特殊的构造函数会调用更通用的构造函数,直到到达最后一个构造函数。 讲白了,其实就是让多余的构造函数之间通过this.构造函数 来进行一个收敛,决绝重复代码,仅此而已。这样改起来,不会造成侵入性过大,维护成本也较小。

例如:

 //银联支付 没有 用券的资格 也没有用贷款支付的资格
    public PayResult(Date payDate, Double totalValue, Double paymentValue) {
        this(new BankChannel(), payDate, totalValue, paymentValue, 0.0d, 0.0d);
    }

    //微信支付没有贷款支付的能力
    public PayResult(Date payDate, Double totalValue, Double paymentValue, Double couponValue) {
        this(new WxChannel(), payDate, totalValue, paymentValue, couponValue, 0.0d);

    }

    //这个就是全包构造函数了 构造函数最全的就可以称之为全包构造函数,当我们的构造函数过多的时候
    //就可以将多余的构造函数都最终指向我们的全包构造函数,这是一个收敛的过程
    public PayResult(PayChannel payChannel, Date payDate, Double totalValue, Double paymentValue, Double couponValue, Double loanValue) {
        this.payChannel = payChannel;
        this.payDate = payDate;
        this.totalValue = totalValue;
        this.paymentValue = paymentValue;
        this.couponValue = couponValue;
        this.loanValue = loanValue;
    }

改完以后就很简单,我们将我们的构造函数进行了统一的收敛,这在业务负载,改动频繁的场景中会变的十分好用, 不会因为增加删除或者变更了某些字段而引发大面积改动。

如果是一个有代码洁癖的人,改到这里也会觉得不舒服,还想继续改。因为这样改完虽然能够部分解决构造函数复杂,维护成本巨大的问题,但是代码的可读性却没有改观。

对于构造函数来说,可读性非常非常重要。本质上来说,如果一个类的构造函数越多,那么程序员犯错的可能性就越高,因为你构造函数太多了,多到你的构造函数本身无法有效和高效的表达你的意图。

而且对于很多长年累月的老项目来说,很多构造函数甚至都过时了,根本没人用。但是因为可读性的问题,敢删掉他们的人不多。

下面继续介绍一种方法,看看能不能在改动不大的情况下,重构我们的代码,把可读性给提高一下。

 //银联支付
    public static PayResult createUnionPayResult(Date payDate, Double totalValue, Double paymentValue) {
        return new PayResult(new BankChannel(), payDate, totalValue, paymentValue, 0.0d, 0.0d);
    }

    //微信支付
    public static PayResult createWxPayResult(Date payDate, Double totalValue, Double paymentValue, Double couponValue) {
        return new PayResult(new WxChannel(), payDate, totalValue, paymentValue, couponValue, 0.0d);
    }

    //支付宝支付
    public static PayResult createAliPayResult(PayChannel payChannel, Date payDate, Double totalValue, Double paymentValue, Double couponValue, Double loanValue) {
        return new PayResult(new AliPayChannel(), payDate, totalValue, paymentValue, couponValue, 0.0d);
    }


    //注意这个时候我们的全包构造函数 变成了private
    private PayResult(PayChannel payChannel, Date payDate, Double totalValue, Double paymentValue, Double couponValue, Double loanValue) {
        this.payChannel = payChannel;
        this.payDate = payDate;
        this.totalValue = totalValue;
        this.paymentValue = paymentValue;
        this.couponValue = couponValue;
        this.loanValue = loanValue;
    }

增加了几个静态的create 函数,然后将我们的全包构造函数进行收敛 设置为private. 极大的限制了调用者的权限, 从而在一定程度上可以避免程序员的犯错。 而且这种改动也不会伤筋动骨,对于老的代码调用处来说,只要更换一下函数名即可:

public class TestMain {
    public static void main(String[] args) {
        PayResult p1 =  PayResult.createUnionPayResult(new Date(), 3.1d, 3.1d);
        PayResult p2 =  PayResult.createAliPayResult(new AliPayChannel(), new Date(), 5.2d, 3.2d, 3.1d, 0.1d);
        PayResult p3 =  PayResult.createWxPayResult(new Date(), 3.1d, 2.1d, 1.0d);

    }
}

这样看起来,不但可读性大大提升,安全性也较好,再也不用担心有其他同事用错了。