WMS的"创建波次"的策略模式 + 责任链模式
一、波次是什么?
在 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 {
// 尾单
}
问题来了:
- 规则会变:以后想加"按仓库分区打包""按货主打包",就得改这个 if-else 大坨代码。
- 规则有顺序,还会互相配合:一个订单先试 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(); // 返回优先级,数字越小越先执行
}
三个关键点:
nextStrategy字段 —— 链上指向「下一个」的指针。process()—— 抽象方法,每个子类必须实现,是策略干活的入口。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 == 1SIFQ_N:totalSku == 1 && totalQuantity == NSIW:totalSku == 1EOW: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引用,串成链,从头开始调。
一句话记住波次创建
筛出匹配订单 → 按商品分组 → 每组够最小订单数就建波次 → 剩余订单传给下一个策略。