预见猿份 · 博客

WMS的"创建波次"的策略模式 + 责任链模式

Java / 设计模式

一、波次是什么?

在 WMS(仓储管理系统)里,仓库一天会接到很多出库单。如果每个出库单都让拣货员单独去仓库跑一趟拣货,效率极低。

于是就有了「波次」这个概念:

波次(Wave)= 把一批出库单"打包"在一起,交给拣货员一次拣完。

「波次策略」常见的几种:

策略类型 含义 匹配条件 优先级(越小越先执行)
SISQ 单件单SKU 订单只有 1 个商品、数量 = 1 1
SIFQ_N 一品 N 件 订单只有 1 个商品、数量 = N 2
SIW 一品(不管数量) 订单只有 1 种商品 3
EOW 尾单 前面策略没挑走的,全收 10

二、为什么这个需求要用两种设计模式?

「创建波次」如果用最笨的 if-else 写,会变成这样:

if (订单是单件单SKU) {
    // 按 SISQ 规则打包
} else if (订单是一品N件) {
    // 按 SIFQ 规则打包
} else if (订单是一品) {
    // 按 SIW 规则打包
} else {
    // 尾单
}

问题来了:

  1. 规则会变:以后想加"按仓库分区打包""按货主打包",就得改这个 if-else 大坨代码。
  2. 规则有顺序,还会互相配合:一个订单先试 SISQ,不行再试 SIW,最后兜底 EOW——这是一种「链式传递」。

所以这里用到了两个设计模式叠加:

  • 策略模式:把每个「打包规则」封装成独立类,每个类自己管自己怎么匹配。
  • 责任链模式:把这些策略按优先级串成一条链,订单从头到尾「流过」这条链,每个策略挑走自己匹配的,剩下的继续往后传。

三、设计模式基础

1. 策略模式

定义一组算法(规则),把它们各自封装起来,让它们可以互相替换。

简单说:把"不同的做法"抽成"不同的类",每个类里写清楚自己怎么做。

2. 责任链模式

让多个处理者按顺序连成一条链,请求沿链传递,每个处理者决定「我处理」还是「交给下一个」。

最经典就是审批流:

项目组长(≤1000) → 部门经理(>1000) → 总经理(>5000)

金额 800 的采购单,组长直接批;金额 10000,组长批不了,往下传,一直传到总经理。

波次创建就是同一条思路,只不过「处理者」换成了「策略」。

四、代码结构总览

wave/
├── strategy/
│   ├── IWaveStrategy.java          # 抽象基类:定义链的骨架
│   ├── WaveCreateClient.java       # 组装链 + 启动链
│   ├── SISQWaveStrategy.java       # 策略1:单件单SKU
│   ├── SIFQWaveStrategy.java       # 策略2:一品N件
│   ├── SIWWaveStrategy.java        # 策略3:一品
│   └── EOWWaveStrategy.java        # 策略4:尾单
└── service/impl/
    └── WmsWaveMasterServiceImpl.java
        ├── createWave(...)         # 总入口
        └── addWave(...)            # 真正建一条波次记录

五、逐个拆解

5.1 责任链骨架:IWaveStrategy

public abstract class IWaveStrategy {
    // 链上的"下一个策略"
    private IWaveStrategy nextStrategy;

    public void setNextStrategy(IWaveStrategy nextStrategy) {
        this.nextStrategy = nextStrategy;
    }

    // 每个策略真正干活的入口(子类实现)
    abstract void process(List<WmsOutOrders> orders,
                          Map<String, List<WmsOutOrdersAllocation>> allocationsMap);

    // 拿到下一个策略
    public IWaveStrategy next() {
        return nextStrategy;
    }

    abstract String getStrategyType();   // 返回策略类型,如 "SISQ"
    abstract int getPriority();          // 返回优先级,数字越小越先执行
}

三个关键点:

  1. nextStrategy 字段 —— 链上指向「下一个」的指针。
  2. process() —— 抽象方法,每个子类必须实现,是策略干活的入口。
  3. getStrategyType() / getPriority() —— 抽象方法,用于「筛选」和「排序」。

5.2 组装链:WaveCreateClient

光有策略类还不够,还得把它们串成链。这个活放在构造方法里:

public WaveCreateClient(List<IWaveStrategy> waveStrategies, List<String> selectedStrategies) {
    // 1. 筛出"用户选中的",并按优先级升序排序
    List<IWaveStrategy> selectedStrategiesList = waveStrategies.stream()
            .filter(strategy -> selectedStrategies.contains(strategy.getStrategyType()))
            .sorted((s1, s2) -> Integer.compare(s1.getPriority(), s2.getPriority()))
            .toList();

    // 2. 链头 = 优先级最高的第一个
    this.firstStrategy = selectedStrategiesList.get(0);

    // 3. 依次把"下一个"设到"上一个"身上,串成链
    for (int i = 1; i < selectedStrategiesList.size(); i++) {
        IWaveStrategy currentStrategy = selectedStrategiesList.get(i - 1);
        currentStrategy.setNextStrategy(selectedStrategiesList.get(i));
    }
}

public void process(List<WmsOutOrders> orders, Map<...> allocationsMap) {
    firstStrategy.process(orders, allocationsMap);   // 从链头开始
}

这段和「审批链」的 Client 构造函数是同一个套路:

projectManager.setNextApprover(departmentManager);
departmentManager.setNextApprover(generalManager);
this.firstApprover = projectManager;

5.3 具体策略:以 SISQ 为例

每个策略的 process 方法都遵循一个固定模板:

public void process(List<WmsOutOrders> orders, Map<...> allocationsMap) {
    // ① 筛出"我匹配的订单"
    List<WmsOutOrders> matchedOrders = orders.stream()
            .filter(order -> order.getTotalSku() == 1 && order.getTotalQuantity() == 1)
            .toList();

    if (matchedOrders.isEmpty()) {
        return;   // 一个都没匹配到,不处理
    }

    // ② 查询本策略的最小订单数配置
    Integer minOrderCount = waveStrategyService.getStrategyByCode(getStrategyType()).getMinOrderCount();

    // ③ 给订单补上 skuId,按商品分组
    Map<String, List<WmsOutOrders>> grouped = matchedOrders.stream()
            .collect(Collectors.groupingBy(WmsOutOrders::getSkuId));

    // ④ 每组够最小订单数就建波次
    grouped.forEach((skuId, orderList) -> {
        if (orderList.size() >= minOrderCount) {
            wmsWaveMasterService.addWave(orderList, getStrategyType(), shipmentStrategy);
        }
    });

    // ⑤ 剩余订单传给下一个策略
    List<WmsOutOrders> remaining = orders.stream()
            .filter(order -> !matchedOrders.contains(order))
            .toList();
    processNext(remaining, allocationsMap);
}

模板核心:

筛 → 分组 → 够数就建波次 → 剩余的传下一个

其他三个策略(SIFQ、SIW、EOW)几乎一模一样,只是「筛选条件」不同:

  • SISQ:totalSku == 1 && totalQuantity == 1
  • SIFQ_N:totalSku == 1 && totalQuantity == N
  • SIW:totalSku == 1
  • EOW:waveId == null && status == ALLOCATED(尾单兜底,全收)

5.4 真正建波次:addWave

策略只是在「挑订单」,真正往数据库写波次记录的是 addWave:

public WmsWaveMaster addWave(List<WmsOutOrders> orders, String strategyType, String shipmentStrategy) {
    // 1. 新建波次对象,填字段
    WmsWaveMaster wave = new WmsWaveMaster();
    wave.setWaveNo(generateWaveNo(strategyType));
    wave.setWaveRuleId(strategyType);
    wave.setStatus(WarehouseDictEnum.WAVE_CREATED.getCode());
    wave.setTotalOrders(orders.size());
    wave.setWarehouseId(orders.get(0).getWarehouseId());

    // 2. 保存波次记录
    save(wave);

    // 3. 给订单打上 waveId(标记"它们属于这个波次")
    List<String> orderIds = orders.stream().map(WmsOutOrders::getId).toList();
    wmsOutOrdersService.batchUpdateWaveId(orderIds, wave.getId(), shipmentStrategy);

    return wave;
}

第 3 步很关键:「订单是否已被打包进波次」就是靠 wave_id 字段判断的。没有 wave_id 就是「还没建波次」。

六、完整执行流程(例子走一遍)

假设有 5 个「已分配库存、还没建波次」的出库单:

  • 订单 A:苹果 ×1(单件单SKU)
  • 订单 B:苹果 ×1(单件单SKU)
  • 订单 C:香蕉 ×1(单件单SKU)
  • 订单 D:苹果 ×2(一品 2 件)
  • 订单 E:苹果 + 香蕉(2 种商品)

用户选中策略:SISQ, SIW, EOW。

Step 1 — 组装链(按优先级排序):

SISQ(1) → SIW(3) → EOW(10)

Step 2 — SISQ 先跑:

筛「单件单SKU」→ 得到 [A, B, C]。按商品分组:

  • 苹果组 [A, B],数量 2 ≥ 最小订单数 → addWave 建波次1
  • 香蕉组 [C],数量 1 < 最小订单数 → 不建

剩余 [C, D, E] → 传给 SIW。

Step 3 — SIW 跑:

筛「一品」→ 得到 [C, D](E 有 2 种商品,不匹配)。每组只有 1 个,都不够 → 一个波次都不建。

剩余 [C, D, E] → 传给 EOW。

Step 4 — EOW 尾单兜底:

全收 [C, D, E] → addWave 建波次2。

最终结果:建了 2 个波次

  • 波次 1 = [A, B](单件单SKU 规则)
  • 波次 2 = [C, D, E](尾单规则)

七、总结:提炼两个套路

策略模式怎么用

有一个 if-else 分支特别多、且每个分支逻辑独立的地方 → 把每个分支抽成一个类,都实现同一个接口/继承同一个抽象类。

责任链模式怎么用

有多个「处理者」需要按顺序处理同一个请求,且每个处理者可以决定「我处理」或「交给下一个」→ 每个处理者持有一个 next 引用,串成链,从头开始调。

一句话记住波次创建

筛出匹配订单 → 按商品分组 → 每组够最小订单数就建波次 → 剩余订单传给下一个策略。

TA 的全部文章 →