zoukankan      html  css  js  c++  java
  • 被各种嵌套判断恶心的你,想到状态模式了吗?

    今天和大家聊『状态模式』这个设计模式,也是由于业务上遇到了一个极其难以维护的订单状态,不得不去重构。

    阿里规约其中就有一条:

    image

    简单来说,状态模式用于消除冗余的大量『if else』判断。

    举个例子

    业务中有一个订单表,其中订单状态大约有如下十多种,我们维护在一个枚举类型中。

    image

    接着,我们有一个 service,用于流转状态用:

    public interface OrderService {
    
        /**
         * 用户发起退款,流转当前订单到退款状态
         * @param order
         * @return
         */
        boolean refund(Order order);
    
        /**
         * 用户取消订单
         * @param order
         * @return
         */
        boolean cancle(Order order);
    }
    

    然后他的实现类是这样:

    @Service
    public class OrderServiceImpl implements OrderService {
    
        @Override
        public boolean refund(Order order) {
            boolean flag = true;
            //判断当前订单状态
            if (order.getOrderState() == OrderState.WAITSHIP.getCode() ||
                    order.getOrderState() == OrderState.PAYED.getCode()){
                //如果是已付款、待发货,直接退款成功
                order.setOrderState(OrderState.REFUND.getCode());
            }else if (order.getOrderState() == OrderState.ALREADYSHIP.getCode() ||
                        order.getOrderState() == OrderState.ENDED.getCode()){
                //如果是已发货了或交易完成了,流转到退款中,等待商家审核
                order.setOrderState(OrderState.REFUNDING.getCode());
            }else {
                //其他状态都是异常状态,拒绝流转
                flag = false;
            }
            return flag;
        }
    
        @Override
        public boolean cancle(Order order) {
            boolean flag = true;
            //判断当前状态
            if (order.getOrderState() == OrderState.NOTPAY.getCode()){
                //只有未付款状态可以取消订单
                order.setOrderState(OrderState.CANCELED.getCode());
            }else {
                flag = false;
            }
            return flag;
        }
    }
    

    一个简单的 refund 流转退款状态至少需要上面这么一大坨的 『if else』判断,下面的 cancle 取消订单状态的流转稍微简单些。

    这里我也只精简了部分代码,实际上要复杂的更多,但好在状态之间的依赖性还没有太强,没有出现嵌套多层『if else』判断,状态模式怎么改?

    状态模式

    传统的判断模式之所以会有很多的『if else』判断,本质上就是不知道当前订单实什么状态,所以需要判断当前订单在不同状态下该怎么流转。

    第一步:创建一个抽象状态基类,在其中定义所有的状态流转操作,这里我只写了两个,实际业务中肯定会有很多很多状态间的跳转。

    @Slf4j
    public abstract class AbstOrderState {
    
        /**
         * 用户发起退款,流转当前订单到退款状态
         * @param order
         * @return
         */
        public boolean refund(Order order){
            log.info("无法从状态:{} 流转到退款状态",getState());
            return false;
        }
    
        /**
         * 用户取消订单
         * @param order
         * @return
         */
        public boolean cancle(Order order){
            log.info("无法从状态:{} 流转到取消订单状态",getState());
            return false;
        }
    
        /**
         * 模板方法,获取当前状态
         * @return
         */
        public abstract String getState();
    }
    

    第二步,为每一种状态创建对应的状态类,并集成抽象状态基类

    image

    第三步,分别实现各个状态下关心的流转操作,我们举例其中两个状态子类的实现。

    这个是已支付状态

    public class PayEdState extends AbstOrderState {
    
        @Override
        public boolean refund(Order order){
            //当前状态是已付款,目标状态是退款
            order.setOrderState(OrderState.REFUND.getCode());
            return true;
        }
    
        //当前状态是已付款,目标状态是取消订单状态,无法流转,异常的状态
        //无需重写,使用抽象基类默认实现,返回失败即可
    //    @Override
    //    public boolean cancle(Order order){
    //        return false;
    //    }
    
        @Override
        public String getState(){
            return OrderState.PAYED.getDesc();
        }
    }
    

    这个是未付款状态

    public class NotPayState extends AbstOrderState {
    
        @Override
        public boolean refund(Order order){
            //当前状态是未付款,目标状态是退款,直接成功
            return true;
        }
    
        @Override
        public boolean cancle(Order order){
            //当前状态是未付款,目标状态是取消订单,直接成功
            return true;
        }
    
        @Override
        public String getState() {
            return OrderState.NOTPAY.getDesc();
        }
    }
    

    第四步,如何使用

    @Test
        public void test(){
            //初始化一个订单,默认未支付状态
            Order order = new Order();
            order.setOrderState(OrderState.NOTPAY.getCode());
    
            //我想退款
            AbstOrderState state = OrderState.getStateByCode(order.getOrderState());
            if (state.refund(order)){
                log.info("ok");
            }else {
                log.info("failed");
            }
        }
    

    至此,状态模式演示完毕,其实细心的你会发现,状态模式中未出现一行『if else』,但缺点就是多了很多类,但这是抽象性的必然结果。

    对比一下

    实际订单状态这个例子并不是很完美契合状态模式,因为状态之间依赖性没那么强,很少可能会出现嵌套判断,但效果是很显然的。

    试想一下,如果以后我的订单增加了一个状态叫『冻结状态』,那么我只需要创建一个新的状态类,并只关心我这个冻结状态相关的流转操作,重写一下就好了,根本不用跑到之前的逻辑里改啊改。

    状态模式还是一个非常优秀的设计模式,推荐大家在项目里使用起来,除了初始编码的时候麻烦一点,后续的维护以及扩展真的近乎零成本。

    近期会整理一个设计模式系列,分别讲讲 23 种设计模式,感兴趣的可以关注下哦~


    关注公众不迷路,一个爱分享的程序员。

    公众号回复「1024」加作者微信一起探讨学习!

    公众号回复「面试题」送你一份面试题以及作者的作答答案

    每篇文章用到的所有案例代码素材都会上传我个人 github

    https://github.com/SingleYam/overview_java

    欢迎来踩!

    YangAM 公众号

  • 相关阅读:
    最近重感冒完全不知道知己在记什么

    倾尽一生
    学习笔记,函数
    唯美句
    02 mysql 基础二 (进阶)
    01 mysql 基础一 (进阶)
    16 正则表达式
    15 迭代器、生成器、模块和包
    14 异常
  • 原文地址:https://www.cnblogs.com/yangming1996/p/14659745.html
Copyright © 2011-2022 走看看