设计模式实战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;
}

五. 总结

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

相关推荐
杨充8 小时前
10.可测试性实战设计
设计模式·开源·代码规范
杨充8 小时前
9.重构十二式的实战
设计模式·开源·代码规范
杨充9 小时前
6.设计原则的全景图
设计模式·开源·全栈
杨充9 小时前
2.面向对象的特性
设计模式
杨充9 小时前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充9 小时前
8.反模式与坏味道
设计模式·开源·代码规范
杨充9 小时前
3.接口vs抽象类比较
设计模式
咖啡八杯12 小时前
文法、BNF与AST
java·设计模式·解释器模式·ast·文法
咖啡八杯20 小时前
GoF设计模式——解释器模式
java·后端·spring·设计模式