复杂业务状态的处理:从状态模式到 FSM

概述

我们平常在开发业务模块时,经常会遇到比较复杂的状态转换。比如说用户可能有新注册、实名认证中、已实名认证、禁用等状态,支付可能有等待支付、支付中、已支付等状态。OA 系统里的状态处理就更多了。遇到这些处理,很多人可能不假思索的就用最直观的 if/else 或者 switch 来判断状态的方式。但其实除了这种简单粗暴的方式,我们还有其他更好的方式来处理复杂的状态转换。

状态判断

我们就以支付为例,一笔订单可能有 等待支付支付中已支付 等状态。对于 等待支付 的订单,用户可能通过第三方支付如微信支付或支付宝进行付款,支付完成后第三方支付会回调通知支付结果。我们可能会这样来处理:

    public void pay(Order order) {
        if (order.status == UNPAID) {
            order.status = PAYING;
            // 处理支付
        } else throw IllegalStateException("不能支付");
    }    
    public void paySuccess(Order order) {
        if (order.status == PAYING) {
            // 处理支付成功通知
            order.status = PAID;
        } else throw IllegalStateException("不能支付");
    }

这样看起来好像没什么问题。但是假设我们允许用户多次支付完成一笔订单,于是我们需要增加一个 部分支付 状态。订单在 部分支付 状态时,可以进行下一步的支付;订单收到支付成功通知时,根据支付金额,可能会转换到 已支付部分支付 状态。现在,我们不得不在 paypaySuccess 里处理这个状态。

    public void pay(Order order) {
        if (order.status == UNPAID || order.status == PARTIAL_PAID) {
            order.status = PAYING;
            // 处理支付
        } else throw IllegalStateException("不能支付");
    }    
    public void paySuccess(Order order) {
        if (order.status == PAYING) {
            // 处理支付成功通知
            if (order.paidFee == order.totalFee) order.status = PAID;
            else order.status = PARTIAL_PAID;
        } else throw IllegalStateException("不能支付");
    }

有了支付,我们必须也要能支持退款,那就需要增加 退款中已退款 状态,以及对应的退款操作和退款成功回调处理。

    public void refund(Order order) {
        if (order.status == PAID || order.status == PARTIAL_PAID) {
            order.status = REFUNDING;
            // 处理退款
        } else throw IllegalStateException("不能退款");
    }
    public void refundSuccess(Order order) {
        if (order.status == REFUNDING) {
            // 处理退款成功通知
            order.status = REFUNDED;
        } else throw IllegalStateException("不能退款");
    }

如果用一个有限状态机(FSM)来表示目前的状态转换,那大概是这样的:
订单支付状态机

对于状态不多、转换也不是很复杂的情况,用状态判断来处理还也算简洁明了。但一旦状态变多,操作变复杂,那么业务代码就会充斥各种条件判断,各种状态处理逻辑散落在各处。这时如果要新增一种状态,或者调整一些处理逻辑,就会比较麻烦,还很容易出错。

例如本例中,实际处理时可能还存在取消订单、支付失败/超时、退款失败/超时等情况,如果再加上物流以及一些内部状态,那处理起来就极其复杂了,而且一不小心还会出现支付失败了还能给用户退款,或者已经退款了还给用户发货等不应该出现的情况。这其实是一种坏味道,会造成代码不易维护和扩展。

设计模式之状态模式

不少人接下来可能会想到