设计模式实战02-责任链模式

设计模式实战-责任链模式

一.场景描述

我们业务开发中,碰到复杂的业务逻辑,有些人会把代码写得非常冗长,代码之间纵横交错,阅读性差且不易拓展和维护,比如下面这段代码(不是我写的--滑稽):

java 复制代码
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateWmsOut(DmsSaleShippingReq req) {
        log.info("销售单-WMS处理的消息通知,监测[{}]", JacksonUtils.obj2String(req));
        // 获取发货单
        DmsSaleShippingDO shippingDO = dmsSaleShippingReadRepository.selectById(req.getId());
        Assert.notNull(shippingDO, "发货单为空, id:" + req.getId());
        Assert.isTrue(req.getShippingCode().equals(shippingDO.getShippingCode()), "发货单编码不一致, id:" +
                req.getId() +
                ", 入参编码:" +
                req.getShippingCode() +
                ", 原始编码:" +
                shippingDO.getShippingCode());
        // 设置销售单ID,后续发消息要用到 勿删
        req.setSaleId(shippingDO.getSaleId());
        // 销售单code,作为分布式锁
        redisDistributedLockTemplate.execute(CodePrefixEnum.DXF.getPrefix() + shippingDO.getSaleCode(), EXPIRE_TIME_FOR_LOCK, () -> {

            // 统计发货单申请数量和发货数量
            BigDecimal shippingApplySum = BigDecimal.ZERO;  // 发货单维度-申请数量
            BigDecimal shippingActualSum = BigDecimal.ZERO; // 发货单维度-实发数量
            List<DmsSaleShippingItemDO> shippingItemDOList = dmsSaleShippingItemService.getDOListBySaleShippingId(shippingDO.getId());
            if (CollectionUtils.isNotEmpty(shippingItemDOList)) {
                for (DmsSaleShippingItemDO itemDO : shippingItemDOList) {
                    shippingApplySum = shippingApplySum.add(itemDO.getApplyNum());
                    shippingActualSum = shippingActualSum.add(itemDO.getActualNum());
                }
            }

            // 统计销售单申请数量和发货数量
            BigDecimal orderAapplySum = BigDecimal.ZERO;    // 销售单维度-申请梳理
            BigDecimal orderActualSum = BigDecimal.ZERO;    // 销售单维度-实发数量
            BigDecimal orderCancelledSum = BigDecimal.ZERO; // 销售单维度-取消数量
            List<SaleOrderItem> orderItemList = saleOrderItemService.getDOListByOrderId(shippingDO.getSaleId());
            if (CollectionUtils.isNotEmpty(orderItemList)) {
                for (SaleOrderItem orderItem : orderItemList) {
                    if (GoodsTypeEnum.GOODS.getCode().equals(orderItem.getIsGift())) {
                        orderAapplySum = orderAapplySum.add(orderItem.getStandardNumber());
                    } else {
                        orderAapplySum = orderAapplySum.add(orderItem.getProductNum());
                    }
                    orderActualSum = orderActualSum.add(orderItem.getDeliveredNum());
                    orderCancelledSum = orderCancelledSum.add(orderItem.getCancelledNum());
                }
            }

            // 记录需要更新的发货单明细
            List<DmsSaleShippingItemDO> updateShippingDoList = Lists.newArrayList();

            // 处理发货单明细
            List<DmsSaleShippingItemReq> itemReqList = req.getItemReqList();
            for (DmsSaleShippingItemReq itemReq : itemReqList) {
                // 获取发货单明细
                DmsSaleShippingItemDO shippingItemDO = dmsSaleShippingItemService.getDOById(itemReq.getId());
                Assert.notNull(shippingItemDO, "发货单详情为空, id:" + itemReq.getId());
                // 统计
                shippingActualSum = shippingActualSum.add(itemReq.getActualNum());
                // 更新发货单明细
                shippingItemDO.setActualNum(shippingItemDO.getActualNum().add(itemReq.getActualNum()))
                        .setWmsOutTime(itemReq.getWmsOutTime())
                        .setUpdateBy(req.getUpdateBy())
                        .setUpdateTime(req.getUpdateTime())
                        .setScanRecord(itemReq.getScanRecord())
                        .setScanExcp(itemReq.getScanExcp())
                        .setScanLitresEnable(itemReq.getScanLitresEnable())
                        .setScanNumEnable(itemReq.getScanNumEnable());
                dmsSaleShippingItemService.updateDOById(shippingItemDO);
                // 重置为发货数量
                shippingItemDO.setActualNum(itemReq.getActualNum());
                updateShippingDoList.add(shippingItemDO);

                // 获取销售单明细
                SaleOrderItem orderItem = saleOrderItemService.getDOById(shippingItemDO.getSaleOrderItemId());
                Assert.notNull(orderItem, "销售单详情为空, id:" + shippingItemDO.getSaleOrderItemId());
                orderActualSum = orderActualSum.add(itemReq.getActualNum());
                // 更新销售单明细
                SaleOrderItem updateOrderItem = new SaleOrderItem();
                updateOrderItem.setId(orderItem.getId())
                        .setDeliveredNum(orderItem.getDeliveredNum().add(itemReq.getActualNum()))
                        .setUpdateBy(String.valueOf(req.getUpdateBy()))
                        .setUpdateTime(req.getUpdateTime());
                saleOrderItemService.updateDOById(updateOrderItem);
            }

            // 更新发货单
            DmsSaleShippingDO updateShippingDO = new DmsSaleShippingDO();
            updateShippingDO.setId(shippingDO.getId())
                    .setShippingStatus(shippingActualSum.compareTo(shippingApplySum) <
                            0 ? ShippingStatusEnum.PART_SHIPPING.getCode() : ShippingStatusEnum.ALL_SHIPPING.getCode())
                    .setUpdateBy(req.getUpdateBy())
                    .setUpdateTime(req.getUpdateTime());
            dmsSaleShippingWriteRepository.updateById(updateShippingDO);

            // 更新销售单
            SaleOrder orderDo = new SaleOrder();
            orderDo.setId(shippingDO.getSaleId())
                    .setOrderStatus(orderActualSum.compareTo(orderAapplySum) <
                            0 ? OrderStatusEnum.PARTIAL_SHIPMENT.getCode() : OrderStatusEnum.SHIPPED.getCode())
                    .setUpdateBy(String.valueOf(req.getUpdateBy()))
                    .setUpdateTime(req.getUpdateTime());

            if (orderDo.getOrderStatus().equals(OrderStatusEnum.SHIPPED.getCode())) {
                orderDo.setDeliveryTime(new Date());
            }
            saleWriteRepository.updateById(orderDo);
            SaleServiceImpl saleService = applicationContext.getBean(SaleServiceImpl.class);
            SaleInfoResp saleInfoResp = saleService.searchOrder(shippingDO.getSaleId());

            // 记录商品还点记录
            List<DmsSaleShippingItemDO> shipItemList = dmsSaleShippingItemService.getDOListBySaleShippingId(shippingDO.getId());
            SaleOrder saleOrder = saleWriteRepository.selectById(shippingDO.getSaleId());
            addSaleOrderItemMapPoint(shipItemList, shippingDO, saleOrder, itemReqList);

            // 更新最近发货时间
            dmsStoreInfoExtendService.updateLastShippingTime(shippingDO.getRecCompanyId(), new Date());
            try {
                // 释放预占接口
                stkCenterReleaseHoldStock(shippingDO, shippingItemDOList);
                // 财务结算单
                Map<Long, BigDecimal> shipItemAmountMap = addSettlementBill(saleInfoResp, shippingDO, updateShippingDoList);
                shipItemAmountMap.forEach((shipItemId, bigDecimal) -> {
                    DmsSaleShippingItemDO shippingItemDO = new DmsSaleShippingItemDO();
                    shippingItemDO.setId(shipItemId);
                    shippingItemDO.setSettlementAmount(bigDecimal);
                    dmsSaleShippingItemService.updateDOById(shippingItemDO);
                    log.info("更新发货单明细的结算金额,明细id{},金额{}", shipItemId, bigDecimal);
                });

                // 全部发货情况
                if (orderActualSum.compareTo(orderAapplySum) >= 0) {
                    // 送积分
                    if (SaleSourceEnum.EOrder.getCode().equals(saleInfoResp.getSource()) &&
                            PaymentStatusEnum.SHIPPED.getCode().equals(saleInfoResp.getPaymentStatus())) {
                        operateAddScore(saleInfoResp);
                    }
                    // 记录销售单状态扭转日志
                    DmsSaleShippingServiceImpl bean = applicationContext.getBean(DmsSaleShippingServiceImpl.class);
                    bean.addAllDipatchLog(shippingDO);
                } else {
                    // 记录销售单状态扭转日志
                    DmsSaleShippingServiceImpl bean = applicationContext.getBean(DmsSaleShippingServiceImpl.class);
                    bean.addPartDipatchLog(shippingDO);
                }

            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        });
        return Boolean.TRUE;
    }

简单说明下上述代码的功能:

上述代码是一个发货单的商品出库的一个消息通知,代码主要做了以下几件事

  • 处理发货单商品数量及状态
  • 处理销售单商品数量及状态
  • 新增还点记录
  • 释放预占的库存
  • 新增财务结算单
  • 赠送客户会员积分
  • 新增销售单的操作日志

我们不难看出,以上代码业务逻辑不算复杂,但是看起来就不舒服, 想要在这个代码上加什么业务逻辑,需要仔细阅读代码. 归结原因是代码责任划分不清晰,哪些逻辑需要什么参数没有明确的说明.这个时候,责任链模式可以很好的解决这些问题.

二.使用责任链模式进行重构

通过分析上述代码,发现整个代码处理逻辑需要的几个数据 无非是:出库通知参数,销售单,销售单商品明细,发货单,发货单商品明细.

1.封装责任链上下文对象

java 复制代码
@Data  
public class SaleDeliveryWmsOutContext {
    private DmsSaleShippingReq req;
    private DmsSaleShippingDO shippingDO;
    private List<DmsSaleShippingItemDO> shippingItemDOList;
    private SaleOrder saleOrder;
    private List<SaleOrderItem> orderItemList
}

2.定义具体职责

处理销售单

java 复制代码
/**  
* 处理销售单  
*/  
private Consumer<SaleDeliveryWmsOutContext> dealSaleOrder() {  
    return context -> {  
        DmsSaleShippingReq req = context.getReq();
        DmsSaleShippingDO shippingDO= context.getShippingDO();
        List<DmsSaleShippingItemDO> shippingItemDOList= context.getShippingItemDOList();
        SaleOrder saleOrder= context.getSaleOrder();
        List<SaleOrderItem> orderItemList= context.getOrderItemList()
        ....
    };  
}

处理发货单

java 复制代码
/**  
* 处理发货单
*/  
private Consumer<SaleDeliveryWmsOutContext> dealSaleDelivery() {  
    return context -> {  
        DmsSaleShippingReq req = context.getReq();
        DmsSaleShippingDO shippingDO= context.getShippingDO();
        List<DmsSaleShippingItemDO> shippingItemDOList= context.getShippingItemDOList();
        SaleOrder saleOrder= context.getSaleOrder();
        List<SaleOrderItem> orderItemList= context.getOrderItemList()
        ....
    };  
}

新增换点记录

java 复制代码
/**  
* 新增换点记录
*/  
private Consumer<SaleDeliveryWmsOutContext> addSaleOrderItemMapPoint() {  
    return context -> {  
        ...
    };  
}

释放预占库存

java 复制代码
/**  
* 释放预占库存
*/  
private Consumer<SaleDeliveryWmsOutContext> stkCenterReleaseHoldStock() {  
    return context -> {  
        ...
    };  
}

新增财务结算单

java 复制代码
/**  
* 新增财务结算单
*/  
private Consumer<SaleDeliveryWmsOutContext> addSettlementBill() {  
    return context -> {  
        ...
    };  
}

赠送客户会员积分

java 复制代码
/**  
* 赠送客户会员积分
*/  
private Consumer<SaleDeliveryWmsOutContext> operateAddScore() {  
    return context -> {  
        ...
    };  
}

三. 按顺序构建职责链

所谓责任链,就是一个List,代码如下:

java 复制代码
private List<Consumer<SaleDeliveryWmsOutContext>> getSaleDeliveryWmsOutActionList() {  
    List<Consumer<SaleDeliveryWmsOutContext>> consumers = new ArrayList<>();  
    consumers.add(dealSaleDelivery());  
    consumers.add(dealSaleOrder());  
    consumers.add(addSaleOrderItemMapPoint());  
    consumers.add(stkCenterReleaseHoldStock());  
    consumers.add(addSettlementBill());  
    consumers.add(operateAddScore());  
    return consumers;  
}

四. 重构后代码

重构后代码

java 复制代码
@Transactional(rollbackFor = Exception.class)
public Boolean updateWmsOut(DmsSaleShippingReq req) {
    //构建责任链上下文
    SaleDeliveryWmsOutContext context = buildSaleDeliveryWmsOutContext(req);
    //获得责任链
    List<Consumer<SaleDeliveryWmsOutContext>> actionList = getSaleDeliveryWmsOutActionList();
    //执行
    for (Consumer<SaleDeliveryWmsOutContext> consumer : actionList) {  
        consumer.accept(context);  
    }
    return Boolean.True;
}

构建上下文对象

java 复制代码
private SaleDeliveryWmsOutContext buildSaleDeliveryWmsOutContext(DmsSaleShippingReq req) {
    //构建责任链上下文
    SaleDeliveryWmsOutContext context = new SaleDeliveryWmsOutContext();
    context.setReq(req);
    ...
    return context;
}

五. 总结

责任链模式最大的好处就是让代码可阅读性变高, 且易于维护. 能让复杂的业务逻辑一目了然;

相关推荐
小飞悟4 小时前
那些年我们忽略的高频事件,正在拖垮你的页面
javascript·设计模式·面试
江上清风山间明月9 小时前
一周掌握Flutter开发--10. 结构与设计模式
flutter·设计模式·快速
牛奶咖啡1311 小时前
学习设计模式《十七》——状态模式
学习·设计模式·状态模式·认知状态模式·状态模式的优缺点·何时使用状态模式·状态模式的使用示例
找了一圈尾巴11 小时前
设计模式(行为型)-责任链模式
设计模式·责任链模式
使一颗心免于哀伤13 小时前
《设计模式之禅》笔记摘录 - 5.代理模式
笔记·设计模式
Patrick_Wilson1 天前
青苔漫染待客迟
前端·设计模式·架构
易元2 天前
设计模式-模板方法模式
后端·设计模式
花好月圆春祺夏安2 天前
基于odoo17的设计模式详解---策略模式
设计模式·策略模式
收破烂的小熊猫~2 天前
《Java修仙传:从凡胎到码帝》第四章:设计模式破万法
java·开发语言·设计模式
佛祖让我来巡山2 天前
【工厂和策略设计模式妙用】解决接口选择与多重if-else 问题
设计模式·策略模式·工厂模式