何时以及为什么要密封一个类

本文关键字:一个 密封 为什么 何时 | 更新日期: 2023-09-27 18:11:32

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

何时以及为什么要密封一个类

  1. 在实现安全特性的类上,使原始对象不能被"模拟"。

  2. 更一般地说,我最近与微软的一个人进行了交流,他告诉我他们试图将继承限制在真正完全有意义的地方,因为如果不加以处理,它将变得昂贵的性能。
    密封的关键字告诉CLR,没有类进一步寻找方法,这加快了速度。

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

对Louis Kottmann精彩回答的补充:

  • 如果一个类不是为继承而设计的,子类可能会破坏类不变量。当然,这只适用于创建公共API的情况,但根据我的经验,我密封任何没有明确设计为子类的类。
  • 一个相关的注意事项,只适用于未密封类:virtual创建的任何方法都是一个扩展点,或者至少看起来应该是一个扩展点。声明方法virtual也应该是一个有意识的决定。(在c#中,这是一个有意识的决定;在Java中则不是。)

    然后是这个:

  • 密封可以使单元测试更加困难,因为它禁止mock。

    相关链接:

    • Effective Java, 2nd Edition by Joshua Bloch。见第17条(需要Safari订阅)
    • Effective Java项目17:设计和文档继承,否则禁止继承(同一项目的讨论)

    还要注意Kotlin默认密封类;它的open关键字与Java的final或c#的sealed相反。(可以肯定的是,没有普遍的共识认为这是一件好事。)

  • 将一个类标记为Sealed可以防止对可能危及安全性或影响性能的重要类进行篡改。

    很多时候,当我们设计一个具有固定行为的实用程序类时,封住类也是有意义的,我们不想改变它。

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

    同样,C#中的structures总是隐式密封的。因此,不能从另一个结构派生出一个结构/类。这样做的原因是structures仅用于对独立的、原子的、用户定义的数据类型建模,我们不想修改这些数据类型。

    有时候,当您在构建类层次结构时,您可能希望根据您的领域模型或业务规则来封盖继承链中的某个分支。

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

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

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

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

    作为一个例子,因为我们不能从它们派生,但它们看起来很像一个普通的类,我们经常把结构体看作是密封类。这就是它们的本质。正是这个限制使它们能够实现值类型语义,因为继承和多态性只能与引用类型一起工作。所以结构类是密封的,因为任何实现值类型语义的类都必须放弃继承并以不同的方式管理其内存。(注意c#中的任何值类型对象都是如此,而不仅仅是结构体)

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

    最后一个例子:一个数学实用程序类可能是封闭的,因为它是围绕真理构建的,任何扩展的行为都不可能是正确的或"按预期工作"。这是一个不符合上述经验法则的例子。永远不要盲目相信经验法则。


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

    如果您的设计模式使用的是单例模式,您可能希望密封类,以便只能创建一个实例。基本上,封装类,创建私有构造函数和公共静态方法来返回类。