密封类通过permits显式限定子类型,使限界上下文边界在编译期可见且不可绕过:非法实现直接编译失败,switch穷尽检查强制处理所有合法变体,record子类保障不可变性,从而将上下文隔离从约定变为类型系统硬约束。

密封类如何让限界上下文边界在编译期“可见”
限界上下文的边界不是靠文档或约定维持的,而是靠类型系统能否阻止跨上下文的非法耦合。密封类通过 permits 显式列出所有允许的子类型,把“这个模型只允许这几种实现”变成编译器可检查的事实。比如设备管理上下文定义了 public sealed interface DeviceState permits Online, Offline, Maintenance{},那任何其他包里试图写 class HackState implements DeviceState 的代码,直接编译失败——这不是风格建议,是硬性拦截。
常见错误现象:团队用抽象类或接口代替密封类,结果在数据采集上下文中悄悄新增一个 RawSensorState 实现,导致设备状态逻辑被意外绕过;或者测试用例里 mock 出非法子类,掩盖了真实上下文隔离失效的问题。
-
permits列表必须与密封类同文件(Java 25 起支持跨文件但需同模块),否则编译报错error: class is not listed in the permits clause - 子类不能是
public且跨包——这是对上下文边界的物理约束,不是访问修饰符技巧 - IDE(如 IntelliJ 2026.1)会高亮未覆盖的密封分支,比注释“此处需更新”可靠得多
为什么 instanceof + 密封类比传统 Visitor 更适合跨上下文交互
当设备管理上下文需要向报警处理上下文传递状态变更事件时,传统做法是定义通用 Event 接口,再靠 instanceof 分支判断类型。问题在于:新增一种状态类型后,所有 instanceof 链都可能漏处理,且无法静态验证。
改用密封类后,switch 表达式能强制穷尽:
return switch (state) {
case Online online -> new AlarmEvent("DEVICE_ONLINE", online.deviceId());
case Offline offline -> new AlarmEvent("DEVICE_OFFLINE", offline.deviceId());
// 编译器会报错:missing switch branch for Maintenance
};
这种机制天然契合限界上下文间的防腐层(ACL)设计:每个上下文只暴露自己认可的密封类型集合,消费方必须显式处理每种合法变体,无法假装“兼容未来类型”。
- 若某上下文需扩展新状态,必须修改密封类的
permits并同步更新所有switch,这恰好触发跨上下文协作流程 - 禁止
default分支——这是关键,它逼你面对“我是否真的理解所有上下文契约” - record 子类自带不可变性,避免跨上下文传递时被意外篡改状态字段
密封接口 vs 密封抽象类:在限界上下文建模中怎么选
核心区别不在语法,而在语义重心:接口强调“能做什么”,抽象类强调“是什么”。领域建模中,前者更适合定义跨上下文契约(如 DeviceCommand),后者更适合封装上下文内共享行为(如 DeviceLifecycle)。
实际踩坑点:
- 用密封抽象类定义跨上下文消息(如
sealed abstract class Command),会导致消费方被迫继承——这违反了上下文解耦原则 - 用密封接口但子类分散在不同模块,Java 25 要求所有
permits类必须在同一个模块声明,否则加载失败 - 记录类(
record)实现密封接口时,字段名自动成为通用语言的一部分(如record Online(String deviceId)中的deviceId是设备管理上下文的术语),强化了限界上下文内的统一语言
非密封子类的使用场景和风险控制
Java 允许对密封类的某个子类声明为 non-sealed,这是为框架扩展留的口子,但必须极度谨慎。例如设备管理上下文开放 non-sealed class CustomState extends DeviceState,意味着允许第三方插件注入状态逻辑。
风险在于:一旦开放,就等于在上下文边界上开了洞。此时必须配套约束:
- 该
non-sealed子类本身不能有 public 构造器,防止任意实例化 - 所有使用该子类的地方,必须用
instanceof显式检查并抛出UnsupportedOperationException,而不是静默忽略 - IDEA 或 SonarQube 可配置规则,禁止在核心业务包中出现
non-sealed声明
真正难的是让团队理解:密封类的价值不在于“禁止一切扩展”,而在于让每一次扩展都成为一次显式的、可审计的、跨上下文对齐的设计决策。那些没出现在 permits 列表里的类型,不是被技术拦住的,是还没通过领域建模共识的。










