When and why would you seal a class?

在 C # 和 C + +/CLI 中,关键字 sealed(或 VB 中的 NotInheritable)用于保护类不受任何继承机会的影响(该类将是不可继承的)。我知道面向对象程序设计的一个特性是继承,我觉得使用 sealed有悖于这个特性,它阻止了继承。 有没有一个例子说明 sealed的好处,以及什么时候使用它是重要的?

50270 次浏览
  1. 在实现安全特性的类上,以便不能“模拟”原始对象。

  2. 更一般地说,我最近与微软的一个人进行了交流,他告诉我,他们试图将继承限制在真正有意义的地方,因为如果不进行处理,性能方面的代价会很高。席尔关键字告诉 CLR 没有类可以进一步查找方法,这加快了速度。

在当今市场上的大多数性能增强工具中,您会发现一个复选框将密封所有未继承的类。
但是要小心,因为如果你想通过 MEF 发现插件或者程序集,你将会遇到问题。

Louis Kottmann's excellent answer的附录:

  1. 如果继承的类不是 设计的,那么子类可能会破坏 类不变量类不变量。当然,这实际上只适用于创建公共 API 的情况,但是根据我的经验,我会密封任何没有明确设计为子类的类。

相关的注意事项,只适用于未密封的类: 任何创建的 virtual方法都是一个扩展点,或者至少看起来应该是一个扩展点。声明方法 virtual也应该是一个有意识的决定。(在 C # 中,这是一个有意识的决定; 在 Java 中则不是。)

还有这个:

  1. 密封可以使单元测试更加困难,因为它禁止模仿。

有关连结:

还要注意,科特林默认密封类; 它的 ABC0关键字与 Java 的 ABC1或 C # 的 sealed相反。(当然,是 没有普遍的共识认为这是一件好事。)

我认为这篇文章有一些好的观点,具体的情况是当试图将一个非密封类强制转换为任何随机接口时,编译器不会抛出错误; 但是当使用密封时,编译器会抛出它无法转换的错误。密封类带来额外的代码访问安全性。
https://www.codeproject.com/Articles/239939/Csharp-Tweaks-Why-to-use-the-sealed-keyword-on-cla

将类标记为 Sealed可以防止篡改可能损害安全性或影响性能的重要类。

Many times, sealing a class also makes sense when one is designing a utility class with fixed behaviour, which we don't want to change.

例如,C#中的 System命名空间提供了许多密封的类,比如 String。如果没有密封,则可以扩展其功能,这可能是不需要的,因为它是具有给定功能的基本类型。

类似地,C#中的 structures始终是隐式密封的。因此,不能从另一个结构派生一个结构/类。这样做的原因是,structures只用于建模 stand-alone, atomic, user-defined数据类型,我们不想修改它。

有时,在构建类层次结构时,可能希望根据域模型或业务规则限制继承链中的某个分支。

例如,ManagerPartTimeEmployee都是 Employee,但是在组织中的兼职员工之后,您就没有任何角色了。在这种情况下,您可能需要密封 PartTimeEmployee以防止进一步分支。另一方面,如果您有每小时或每周一次的兼职员工,那么从 PartTimeEmployee继承他们可能是有意义的。

密封是一个有意识的决定,只有当你想清楚地揭示你的意图关于你的类的结构特征时才应该考虑。它是关于对象模型的结构选择。它永远不应该是一个关于性能或安全性(* *)的决定。但更重要的是,永远不要随意限制您的继承树。

我提出这条经验法则: 如果您必须考虑密封类是否是一个好主意,那么就不应该密封它。密封类的决定对您来说应该是显而易见的,甚至在您编写类的第一行代码之前就已经做出了决定。

例如,由于我们不能从它们派生,但是它们看起来非常像一个常规类,所以我们通常认为结构是密封类。他们就是这样的人。正是这个限制允许它们实现值类型语义,因为继承和多态只能用于引用类型。因此,“ struct 类”是密封的,因为任何实现值类型语义的类都必须放弃继承,并以不同的方式管理其内存。(注意,C # 中的任何值类型对象都是如此,而不仅仅是 structs)。

另一个例子: 代码生成器可以编写一个密封的类,表示一个窗口及其所有元素,供用户在其上定义行为,因为 UI 引擎需要这个类,而不需要其他类,以便能够呈现窗口。

最后一个例子: 一个数学实用工具类可能是密封的,因为它是围绕着老生常谈构建的,任何扩展行为都不可能是正确的或者“按预期工作”。这是一个不完全属于上述经验法则的例子。永远不要盲目地相信经验法则。


(* *)如果性能是应用程序中的一个问题,那么可以肯定未密封的类不是原因。类似地,如果依赖密封类来强制应用程序中的安全性,那么问题必须出现在基类上——它们公开或允许扩展的内容。