你提供的代码是一个 **排程快照类 `ScheduleSnapshot`**,用于在任务调度过程中保存当前状态的“深拷贝”,以便后续可以回滚到该状态。这在回溯算法、试探性排程、撤销操作等场景中非常常见。
下面我将 **逐行解析这个类的设计与实现**,解释其作用、每个字段的意义、拷贝方式的选择以及为何要这样设计。
---
## ✅ 类总览
```java
public static class ScheduleSnapshot {
// 保存多个状态映射的副本
private final Map<String, Map<Date, BigDecimal>> remainingTimeMap;
private final Set<String> filledClasses;
private final Map<String, Set<String>> filledClasses2;
private final Map<String, Set<String>> deviceOccupyMap;
private final Map<String, List<PpMachiningOrderList>> assignedCache;
private final Map<String, Map<Date, List<String>>> tem;
// 构造函数:深拷贝传入的所有状态
public ScheduleSnapshot(...)
// 恢复方法:把快照中的数据写回原始对象
public void restore(...)
}
```
这是一个典型的 **Memento(备忘录)模式** 的应用:保存系统某一时刻的状态,供之后恢复使用。
---
## 🧩 字段详解(每个字段代表什么?)
| 字段 | 类型 | 含义 |
|------|------|------|
| `remainingTimeMap` | `Map<String, Map<Date, BigDecimal>>` | 表示某种资源(如设备、工序)在不同日期的剩余可用时间。例如:`"MachineA"` → `Date: 剩余工时(BD)` |
| `filledClasses` | `Set<String>` | 已被“填满”的类别集合(可能是班次、班组、产线等),一旦填满就不允许再分配任务 |
| `filledClasses2` | `Map<String, Set<String>>` | 更复杂的填充关系,比如 `"Line1"` → `{"ShiftA", "ShiftB"}`,表示某条线上哪些班次已满 |
| `deviceOccupyMap` | `Map<String, Set<String>>` | 设备占用情况,如 `"DeviceX"` → `{"Order123", "Order456"}`,记录哪些订单占用了该设备 |
| `assignedCache` | `Map<String, List<PpMachiningOrderList>>` | 缓存已分配的任务,按 key 分组(可能是设备、工人、工序),保存当前已安排的加工单列表 |
| `tem` | `Map<String, Map<Date, List<String>>>` | 临时结构,可能用于存储每日排班或临时占位信息,如 `"WorkerGroup"` → `Date → ["Order1", ...]` |
这些字段共同构成了一个 **完整的排程上下文状态**。
---
## 🔐 构造函数:为什么要做深拷贝?
构造函数接收原始状态,并通过一系列 `deepCopyXxx()` 方法进行 **深度复制**,确保:
> 快照中的数据独立于原对象,后续修改不会互相影响。
### ❗关键点:浅拷贝 vs 深拷贝
如果不做深拷贝,只复制引用,那么当你修改原始数据时,快照里的数据也会变 —— 这是灾难性的!
所以必须层层拷贝到底层可变对象。
---
## 🔍 各 `deepCopy` 方法详解
### 1. `deepCopyRemainingTimeMap`
```java
private Map<String, Map<Date, BigDecimal>> deepCopyRemainingTimeMap(Map<String, Map<Date, BigDecimal>> src) {
return src.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> new HashMap<>(e.getValue()) // 第二层 map 是新 HashMap
));
}
```
✅ **双层深拷贝完成**:
- 外层 key 不变(String)
- 内层 `Map<Date, BigDecimal>` 是新建的,不影响原 map
📌 注意:`BigDecimal` 是不可变对象,无需再拷贝。
---
### 2. `deepCopyFilledClasses2` 和 `deviceOccupyMap`
```java
private Map<String, Set<String>> deepCopyFilledClasses2(Map<String, Set<String>> src) {
return src.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> new HashSet<>(e.getValue())
));
}
```
✅ 对 `Map<String, Set<String>>` 类型做了深拷贝:
- 外层 map 新建
- 每个 value(Set)也是新的 HashSet
⚠️ 注意:`deviceOccupyMap` 使用了相同的拷贝方法,说明它和 `filledClasses2` 结构一致。
---
### 3. `deepCopyTem`
```java
private Map<String, Map<Date, List<String>>> deepCopyTem(Map<String, Map<Date, List<String>>> src) {
return src.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
innerE -> new ArrayList<>(innerE.getValue())
))
));
}
```
✅ 三层结构全部深拷贝:
- 外层:`String → Map`
- 中间层:`Date → List`
- 最内层:`List<String>` 是新 ArrayList
📌 `String` 不可变,无需拷贝;但 `List` 可变,必须新建。
---
### 4. `deepCopyAssignedCache`
```java
private Map<String, List<PpMachiningOrderList>> deepCopyAssignedCache(Map<String, List<PpMachiningOrderList>> src) {
return src.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> new ArrayList<>(e.getValue()) // 浅拷贝:新建 list,但对象引用相同
));
}
```
❗注意注释:“**浅拷贝:新建 list,但对象引用相同**”
这意味着:
- 列表本身是新的(你可以增删不影响原列表)
- 但里面的 `PpMachiningOrderList` 对象是同一个实例!
👉 是否安全取决于业务逻辑:
| 场景 | 是否安全 |
|------|---------|
| 只读访问这些对象 | ✅ 安全 |
| 修改对象内部字段(如 setQty, setStatus) | ❌ 危险!会影响原状态 |
🔧 如果需要完全隔离,应实现对象克隆(如序列化拷贝、手动 new 或使用 BeanUtils.copyProperties 等)
---
## 💾 `restore()` 方法:如何恢复状态?
```java
public void restore(
Map<String, Map<Date, BigDecimal>> remainingTimeMap,
Set<String> filledClasses,
Map<String, Set<String>> filledClasses2,
Map<String, Map<Date, List<String>>> tem,
Map<String, Set<String>> deviceOccupyMap,
Map<String, List<PpMachiningOrderList>> assignedCache) {
// 清空原始容器,然后放入快照中的副本
remainingTimeMap.clear();
remainingTimeMap.putAll(this.remainingTimeMap);
filledClasses.clear();
filledClasses.addAll(this.filledClasses);
// ... 其他类似
}
```
✅ 恢复策略:
- 所有外部传入的“状态容器”都被清空
- 然后用快照中的副本重新填充
📌 这是一种 **整体替换式恢复**,适用于你在尝试某个排程分支失败后想“回到上一个稳定状态”。
---
## 🎯 使用场景举例
```java
// 保存当前状态
ScheduleSnapshot snapshot = new ScheduleSnapshot(remainingTimeMap, filledClasses, ..., assignedCache);
try {
// 尝试排程某些订单
boolean success = tryScheduleSomeOrders(...);
if (!success) {
// 回滚!
snapshot.restore(remainingTimeMap, filledClasses, ..., assignedCache);
}
} catch (Exception e) {
// 出错也回滚
snapshot.restore(...);
}
```
这在以下场景特别有用:
- 排程试探失败需回退
- 支持“撤销”功能
- 多路径搜索最优解(回溯法)
---
## ✅ 总结:优点与建议
### ✔️ 优点
| 特性 | 说明 |
|------|------|
| **状态隔离** | 通过深拷贝避免共享可变状态 |
| **恢复能力** | 支持完整状态回滚 |
| **结构清晰** | 字段命名规范,职责明确 |
| **扩展性强** | 易于添加新状态字段 |
### ⚠️ 可改进点
1. **`assignedCache` 的浅拷贝风险**
- 建议增加注释警告:“若修改 PpMachiningOrderList 实例字段会导致状态污染”
- 或者改为深拷贝(如果对象不多):
```java
e.getValue().stream().map(deepCopyOrder).collect(Collectors.toList())
```
2. **性能考虑**
- 深拷贝成本高,频繁创建快照会影响性能
- 可考虑差量备份(只记录变化部分)
3. **Date 的不可变性问题**
- `Date` 是可变对象(非 final),虽然这里没改,但建议未来用 `LocalDateTime`
4. **泛型提取成工具类**
- 很多深拷贝逻辑可以封装为通用工具方法,如:
```java
CollectionUtils.deepCopyMap(map, HashSet::new);
```
---
###