如何利用 JDK 17 的密封类(Sealed Classes)构建领域驱动设计中的受限继承体系
如何利用 JDK 17 的密封类(Sealed Classes)构建领域驱动设计中的受限继承体系 说起 Ja va 17 的密封类,很多人的第一反应可能是“增强版的 final”。其实不然,它更像是一份在编译期就强制执行的继承契约。它的真正价值在于,能让你在领域模型中清晰地宣告:“只有这几种状态或类
如何利用 JDK 17 的密封类(Sealed Classes)构建领域驱动设计中的受限继承体系

说起 Ja va 17 的密封类,很多人的第一反应可能是“增强版的 final”。其实不然,它更像是一份在编译期就强制执行的继承契约。它的真正价值在于,能让你在领域模型中清晰地宣告:“只有这几种状态或类型是合法的”,从而彻底告别靠文档或口头约定来约束的松散时代。
密封类必须显式声明 permits,且子类必须标注继承策略
这里有个关键细节:在 Ja va 17 中,permits 子句是必须显式声明的(自动推导功能要等到 Ja va 21+)。这意味着,任何遗漏、拼写错误,或者子类不在同一模块或包内,都会直接导致编译失败。这可不是运行时才抛出的检查,而是硬性的语法门槛。
permits后面列出的每一个子类,都必须在代码中真实存在,并且不能是内部类(静态嵌套类除外)。- 每个被许可的子类,都必须用
final、sealed或non-sealed中的一个来明确修饰,三者缺一不可。 - 如果子类本身也是
sealed,那么它也必须携带自己的permits列表,从而形成一条可延续、受控的继承链。
领域建模时,优先用 final 子类封住具体实现分支
在进行领域驱动设计时,像 PendingStatus(待处理状态)、ShippedStatus(已发货状态)这类具体的值对象或状态,通常就不应该再被继承——它们代表一个确定的、完整的业务语义,额外的扩展只会引入歧义和混乱。
- 此时,使用
final修饰是最安全、最直接的选择,可以有效防止下游使用者意外覆写核心的行为逻辑。 - 通用的抽象方法(例如
canTransitionTo(Status next))应该在密封父类中定义,然后由各个final子类去实现自己特有的状态转换规则。 - 切记,不要为了追求形式上的统一而把所有子类都设为
sealed。如果一个分支在业务上确实没有继续细分的必要,那么用final来表达“到此为止”的意图会更加清晰。
non-sealed 不是后门,而是有明确开放意图的设计信号
那么,non-sealed 是留给开发者的“后门”吗?恰恰相反,它是一个强烈的设计信号。举个例子,在订单领域,OrderStatus(订单状态)密封类可能允许 RefundedStatus(已退款状态)和 CancelledStatus(已取消状态)。但 RefundedStatus 本身可能需要支持不同的退款渠道,比如 AlipayRefund(支付宝退款)、WechatRefund(微信退款)。这时,将 RefundedStatus 标记为 non-sealed,就等于明确宣告:“这个特定的状态分支,允许在当前的业务上下文内进行有控制的扩展”。
non-sealed子类依然受到密封父类的保护:外部包无法直接继承顶层的OrderStatus,只能继承你特意放开的那个特定分支。- 它不会破坏整体的密封性,只是一种局部的、有意的解耦设计。编译器仍然能对其他
final或sealed的分支进行穷尽性检查。 - 当然,滥用
non-sealed(比如给所有子类都加上)会让密封机制形同虚设,失去其核心价值。
和模式匹配配合时,switch 才真正“安全”
密封类的威力,在与模式匹配结合的 switch 表达式/语句中才得以完全展现。因为编译器确切地知道所有可能的子类型,所以它能强制校验你的 switch 是否覆盖了 permits 列表中的全部情况。
- 当你写下
switch (status) { case PendingStatus p -> ... }这样的代码时,如果漏掉了任何一个被许可的子类,编译器会直接报错。 - 你不再需要添加一个“包罗万象”的
default分支(当然,如果主动添加了,就会失去编译器的穷尽性保障)。 - 未来如果在
permits列表中新增子类,所有相关的switch代码都必须同步更新,否则无法通过编译——这是一种由工具保障的强一致性,远比依赖人工代码审查来得可靠。
所以说,真正的挑战往往不在于语法本身,而在于如何做出精准的设计判断:哪些类应该被密封?哪些子类应该用 final 彻底封闭?哪些又确实需要 non-sealed 来提供有限的扩展性?这完全取决于你对领域边界和业务演化方向的理解深度。错误地将本该封闭的分支放开,或将需要灵活性的分支锁死,都会导致领域模型要么失控,要么僵化。这才是用好密封类的关键所在。


































