责任链模式实践
您目前处于:  2019-04-18

坏味道代码

最近在开发过程中需要修改这样一段代码:

InvoiceSubmitter invoiceSubmitter;
if (isBizBook) { // 图书业务
    if (isVatInvoice) { // 增值税专用发票
        invoiceSubmitter = rjVatInvoiceSubmitter;
    } else { // 增值税普通发票
        invoiceSubmitter = rjGeneralInvoiceSubmitter;
    }
} else {
    invoiceSubmitter = commonInvoiceSubmitter; // 通用发票申请
}
invoiceSubmitter.submitRequisition(order); 

关于 InvoiceSubmitter 类图如下:

OrderFinishSupport 直接关联 InvoiceSubmitter 接口,AbstractInvoiceSubmitter 实现 InvoiceSubmitter 接口的 submitRequisition 方法封装通用逻辑,并定义另外一个重载的 submitRequisition 方法供子类实现具体业务逻辑。

业务逻辑

这是一段发票提交的逻辑,在我们系统的订单完成之后,需要根据用户提交的发票内容调用不同的接口将发票信息提交到发票系统,发票系统来完成为用户开具发票的工作。

这段代码目前看起来还算清晰,就是根据业务和发票的类型判断调用哪个接口,但是这次需要修改这段代码,加上分支判断逻辑区分另外一种业务,未来还会承接更多的需求,这样这段代码的条件分支判断会越来越庞大,可读性会越来越差,也越来越难维护。

具体设计

重构开始,这段逻辑整体看起来就是一条链,每个 submitter 处理满足自己条件的请求,所以责任链模式可以很好的解决这个问题,重构之后结构如下:

抽象 InvoiceSubmitHandler,OrderFinishSupport 改为关联 InvoiceSubmitHandler,在 InvoiceSubmitHandler 的初始化方法 init 中调用各个 submitter 的 setNextHandler 方法构建好整个责任链,每个具体的 submitter 通过 match 方法返回自己是否能够处理本次请求的波尔值,AbstractInvoiceSubmitter 会根据这个返回值判断用当前的 submitter 处理还是调用下一个 nextHandler 来处理。

代码实现

关键代码如下:

class InvoiceSubmitHandler {

    private InvoiceSubmitter commonInvoiceSubmitter;
    private InvoiceSubmitter rjGeneralInvoiceSubmitter;
    private InvoiceSubmitter rjVatInvoiceSubmitter;

    /**
     * 初始化链条,设置handler的下一个handler
     */
    public void init() {
        commonInvoiceSubmitter.setNextHandler(rjGeneralInvoiceSubmitter);
        rjGeneralInvoiceSubmitter.setNextHandler(rjVatInvoiceSubmitter);
    }

    /**
     * 提交发票申请单
     */
    public void submitRequisition() {
        commonInvoiceSubmitter.submitRequisition();
    }
}
abstract class AbstractInvoiceSubmitter implements InvoiceSubmitter {

    /**
     * 下一个handler
     */
    private InvoiceSubmitter nextHandler;

    /**
     * 提交申请单
     */
    public void submitRequisition() {
        if (match()) {
            submitRequisition();
            return;
        }
        if (nextHandler == null) {
            // 如果下一个handler为空,证明当前handler已经是最后一个handler
            // 直接返回,证明本次请求不需要处理
            return; 
        }
        nextHandler.submitRequisition();
    }

    /**
     * 判断当前handler是否能够处理当前请求
     */
    protected abstract boolean match();

    /**
     * 设置下一个handler
     */
    public void setNextHandler(InvoiceSubmitter handler) {
        this.nextHandler = handler;
    }

    /**
     * 子类具体实现提交发票申请单逻辑
     */
    protected abstract void submitRequisition();
}
class RJGeneralInvoiceSubmitter extends AbstractInvoiceSubmitter {

    /**
     * 判断当前handler是否能够处理当前请求
     */
    protected boolean match() {
        return 是图书业务 && 发票类型为增值税普通发票;
    }

    /**
     * 提交申请单
     */
    public void submitRequisition() {
        ...
    }
}

具体submitter只列出一个,其他类似。

总结

重构之后,具体的匹配逻辑分散到各个子类中实现,类的职责更加明确和集中,代码可读性、扩展性更好。在使用责任链模式的时候有一个需要注意的点,我们需要保证整个链一定要有一个节点能够处理当前请求,不然在请求传递到最后一个节点还处理不了时,会报空指针异常,因为最后一个节点的 nextHandler 没有设置,我们这里的处理是判断如果 nextHandler 为空,证明当前节点已经是最后一个,直接返回,证明本次请求不需要提交发票申请。也可以定义一个可以匹配所有请求的最终节点,当前面的所有节点都无法处理的时候由这个节点做一个默认处理。


本文受原创保护,未经作者授权,禁止转载。 linkedkeeper.com (文/张强)  ©著作权归作者所有