密封类用于强制类型闭合,将审批节点类型收束于编译期,确保引擎静态识别所有合法节点;配合when实现穷尽调度,绑定状态与行为,并需正确序列化以保障跨服务传递。

密封类不是用来“支持多态扩展”的,而是用来“强制类型闭合”的——它把审批节点的合法形态提前收束在编译期,让引擎能静态识别所有可能的节点类型,从而避免运行时误跳、非法流转或审计缺失。
用密封类定义审批节点的有限类型体系
审批节点不是任意字符串或枚举值,而应是可携带上下文、可区分语义、可参与模式匹配的类型。例如:
-
普通审批节点:如
class ApproveNode(val role: String) : ApprovalStep() -
驳回节点:如
class RejectNode(val targetRole: String, val reason: String) : ApprovalStep() -
加签节点:如
class AddSignNode(val additionalRole: String) : ApprovalStep() -
会签节点:如
object ConcurrentReview : ApprovalStep()(无状态、复用性强)
所有子类必须与 sealed abstract class ApprovalStep 同文件定义,禁止外部新增节点类型,确保“就这些”。
配合 when 实现节点行为的穷尽调度
工作流引擎执行时,不再靠 if-else 或字符串 switch 判断节点类型,而是直接对 ApprovalStep 做 when 分支处理:
- 每个分支对应一种节点语义,比如
is RejectNode -> jumpTo(targetRole) - 编译器强制覆盖全部子类;若后续新增
EscalateNode,所有未更新的when立即报错 - 避免漏写驳回逻辑、加签校验或会签聚合规则等关键路径
将节点类型与流程实例状态绑定
密封类天然适合建模状态迁移中的“动作+目标”组合:
- 流程实例当前处于
ApproveNode("tech-leader"),表示等待技术负责人审批 - 用户点击“驳回”,生成
RejectNode("product-manager", "需求不完整"),作为下一步指令 - 引擎根据该类型自动触发清理任务、写入批注、重置指针并生成新待办
这种设计把“节点是什么”和“接下来做什么”耦合在类型内部,而非散落在配置表或硬编码条件里。
对接序列化与跨服务传递
密封类在 Spring 或 Flowable 中需正确序列化,否则节点类型信息会在远程调用中丢失:
- Kotlin 中为每个子类添加
@Serializable,主类启用@Serializable(with = ApprovalStepSerializer::class) - Java 17 可用
sealed interface ApprovalStep+record ApproveNode(String role) implements ApprovalStep,天然支持 JSON 多态反序列化 - 避免用
Object或泛型擦除接收节点,坚持用密封父类型声明参数和返回值
不复杂但容易忽略











