OptaPlanner的VariableListener中修改其他实体影子变量是否有问题
OptaPlanner VariableListener修改非入参实体影子变量的问题解答
这种操作本身是合法的,在不少业务场景下甚至是必要的,但必须严格遵守OptaPlanner的VariableListener开发规范,否则会引发评分异常、求解不稳定等问题。
适用场景举例
以任务分配场景的链式规划为例:当某一个任务的前置任务/所属员工发生变更时,该员工名下所有排在该任务之后的任务的开始/结束时间都会受影响,此时就必须在该任务的变更监听中批量更新后续其他任务的影子变量。
必须遵守的开发规范
如果要在afterVariableChanged中修改其他实体的影子变量,必须满足以下要求:
- 所有对其他实体影子变量的修改,必须主动通知ScoreDirector:修改前调用
scoreDirector.beforeVariableChanged(修改的实体, "影子变量字段名"),修改完成后调用scoreDirector.afterVariableChanged(修改的实体, "影子变量字段名"),不能直接修改实体属性不做通知,否则OptaPlanner无法感知该变更。 - 避免循环触发:不要出现实体A的监听修改实体B,实体B的监听又回过来修改实体A的逻辑,否则会触发无限递归导致栈溢出。
- 保证逻辑幂等:相同的规划变量状态下,无论监听被触发多少次,最终所有影子变量的计算结果必须完全一致,否则会导致求解过程中评分异常波动,无法得到最优解。
正确示例代码
@Override public void afterVariableChanged(ScoreDirector<TaskAssigningSolution> scoreDirector, Task changedTask) { Employee assignedEmployee = changedTask.getEmployee(); if (assignedEmployee == null) { return; } // 筛选出同属该员工、排在当前变更任务之后的所有任务 List<Task> subsequentTaskList = assignedEmployee.getTaskList() .stream() .filter(task -> task.getSequence() > changedTask.getSequence()) .toList(); Task previousTask = changedTask; for (Task task : subsequentTaskList) { // 通知ScoreDirector即将修改变量 scoreDirector.beforeVariableChanged(task, "startTime"); task.setStartTime(previousTask.getEndTime()); // 通知ScoreDirector修改变量完成 scoreDirector.afterVariableChanged(task, "startTime"); previousTask = task; } }
违规操作的常见后果
如果不遵守上述规范直接修改其他实体的影子变量,大概率会出现以下问题:
- 增量评分计算错误,最终得到的求解结果不符合业务约束
- 影子变量变更无法被其他关联的VariableListener感知,导致后续的影子变量更新遗漏
- 求解过程中状态不一致,出现异常报错
内容的提问来源于stack exchange,提问作者brewphone
相关产品推荐
相关产品推荐

