【问题标题】:Why seal a class?为什么要封类?
【发布时间】:2010-09-21 01:28:08
【问题描述】:

我想知道 .Net 框架中大量密封类背后的动机是什么。密封类有什么好处?我无法理解不允许继承如何有用,而且很可能不是唯一一个与这些类作斗争的人。

那么,为什么要这样设计框架,而解封所有内容不是不可改变的变化吗?除了邪恶之外,肯定还有别的原因吧?

【问题讨论】:

标签: .net performance


【解决方案1】:

类应该设计成继承或禁止继承。设计继承是有代价的:

  • 它可以确定您的实现(您必须声明哪些方法将调用其他哪些方法,以防用户覆​​盖其中一个而不是另一个)
  • 它揭示了您的实现,而不仅仅是效果
  • 这意味着您在设计时必须考虑更多可能性
  • Equals 之类的东西很难在继承树中设计
  • 需要更多文档
  • 子类化的不可变类型可能变为可变 (ick)

Effective Java 的第 17 项对此进行了更详细的介绍 - 不管它是在 Java 上下文中编写的,但该建议也适用于 .NET。

我个人希望类在 .NET 中默认密封。

【讨论】:

  • 嗯..如果你扩展一个类,如果你破坏它不是你的问题吗?
  • 如果在基类中您无法控制的实现更改破坏了您怎么办?那是谁的错?基本上,继承引入了脆弱性。 IMO,优先组合而不是继承可以提高鲁棒性。
  • 是的,界面很好 - 是的,无论如何你都可以喜欢组合。但是,如果我在没有仔细考虑的情况下公开了一个未密封的基类,我应该预料到更改很可能会破坏派生类。这对我来说是件坏事。最好密封类并避免破损,IMO。
  • @Joan:组合是一种“has-a”关系,而不是“is-a”。因此,如果您想编写一个在某些方面可以像列表但在其他方面不能像列表一样的类,您可能想要创建一个具有 List 成员变量的类,而不是从 List 派生。然后,您将使用该列表来实现各种方法。
  • 我自己,我很少使用继承。通常,我更喜欢作曲。但是,继承是 OOP 的一个非常重要的特性。基类的实现应该与派生类无关。基类的接口不应以使受保护或公共成员无效的方式更改。如果一个类不适合继承,那么它就是一个设计糟糕的类,首先。任何不会破坏现有代码的类更改也不应该破坏派生类。
【解决方案2】:
  • 有时类太宝贵了,不能被继承。
  • 在查找类型时,运行时/反射可以对密封类进行继承假设。一个很好的例子是 - 建议密封属性以提高查找运行时的速度。如果 MyAttribute 是密封的,type.GetCustomAttributes(typeof(MyAttribute)) 将执行得更快。

此主题的 MSDN 文章是 Limiting Extensibility by Sealing Classes

【讨论】:

  • 很高兴看到他们现在清楚地说“谨慎使用”......但希望他们能实践他们所宣扬的。
  • 这对我来说似乎是个坏建议 :(
  • @CVertex:对不起,我不是想批评你——只是文章。
  • @generalt:我相信设计继承或禁止继承。为继承而设计需要做很多工作,并且通常会限制未来的实现。继承还给调用者带来了不确定性,即他们将调用什么。它也不能很好地与不变性(我很喜欢)混合在一起。我只发现类继承在少数地方有用(而我喜欢接口)。
  • @CVertex 如果您使用过 .NET,您可能会遇到问题,只是没有注意到,几乎所有 .NET 核心类都是密封的。
【解决方案3】:

似乎official Microsoft guidelines on sealing 自从大约 9 年前提出这个问题以来已经发展,他们从选择加入哲学(默认密封)转变为选择退出(默认不密封):

X 不要在没有充分理由的情况下密封课程。

密封类,因为您无法想到可扩展性场景 不是很好的理由。框架用户喜欢从类继承 各种不明显的原因,例如添加便利成员。看 Unsealed Classes 用于用户想要的非显而易见原因的示例 从类型继承。

封闭课程的充分理由包括:

  • 该类是静态类。请参阅静态类设计。
  • 该类将安全敏感的机密存储在继承的受保护成员中。
  • 该类继承了许多虚拟成员以及密封它们的成本 个人会超过离开班级的好处 开封。
  • 类是一个属性,需要非常快的运行时间 抬头。密封属性的性能水平略高 比未密封的。请参阅属性。

X DO NOT在密封类型上声明受保护或虚拟成员。

根据定义,密封类型不能被继承。这意味着 不能调用密封类型上的受保护成员,并且虚拟 密封类型的方法不能被覆盖。

✓ 考虑您覆盖的密封成员。引入虚拟会员可能导致的问题(在虚拟会员中讨论) 也适用于覆盖,尽管程度略低。 从那开始,密封一个覆盖可以保护您免受这些问题的影响 继承层次结构中的点。

确实,如果你search the ASP.Net Core codebase,你只会发现sealed class的出现大约30个,其中大部分是属性和测试类。

我确实认为不变性守恒是支持密封的一个很好的论据。

【讨论】:

    【解决方案4】:

    我在 msdn 文档中找到了这句话:“密封类主要用于防止派生。因为它们永远不能用作基类,所以一些运行时优化可以使调用密封类成员的速度稍快一些。”

    我不知道性能是否是密封类的唯一优势,我个人也想知道其他任何原因......

    【讨论】:

    • 看看他们在谈论什么样的性能优势会很有趣......
    【解决方案5】:

    性能是一个重要因素,例如,java 中的字符串类是 final(http://blogs.msdn.com/ericlippert/archive/2004/01/07/virtual-methods-and-brittle-base-classes.aspx

    如果你提供一个框架,那么对于遗留项目的可维护性和升级你的框架以避免脆弱的基类问题很重要

    【讨论】:

    • java中String之所以是final的原因不是性能,而是安全性。
    • @CesarB:是的,但 String 也不是一个普通的 Java 类。它是 Java 中唯一(我相信)支持运算符重载的类(有关更多信息,请参阅 here 部分:“即使 C 和 Java 也有(硬编码)运算符重载”),这在普通类中是不可能的。因此,String 类甚至可能无法子类化,即使它不是最终的。
    【解决方案6】:

    Sealed 用于防止“脆弱的基类问题”。我在 MSDN 中找到了一个 good article 来解释这一点。

    【讨论】:

      【解决方案7】:

      密封允许您实现一些小的性能提升。这在 JIT 和惰性悲观化的世界中不如在 C++ 的世界中那么正确,但由于 .NET 不如 Java 编译器的悲观化那么好,主要是因为不同的设计理念,它仍然有用。它告诉编译器它可以直接调用任何虚方法,而不是通过vtable间接调用它们。

      当您想要一个“封闭世界”来进行平等比较等事情时,这一点也很重要。通常,一旦我定义了一个虚拟方法,我就会非常想定义一个真正实现这个想法的相等比较的概念。另一方面,我也许可以使用虚拟方法为类的特定子类定义它。密封该类可确保平等确实成立。

      【讨论】:

        【解决方案8】:

        密封类可以更轻松地管理一次性资源。

        【讨论】:

          【解决方案9】:

          判断是否封装类、方法或属性,一般要考虑以下两点:

          •通过自定义类的能力,派生类可能获得的潜在好处。

          •派生类可能会修改您的类,使其不再正常工作或无法按预期工作。

          【讨论】:

            【解决方案10】:

            进一步的考虑是密封类不能在您的单元测试中存根。来自Microsoft's documentation

            不能对密封类或静态方法进行存根,因为存根类型依赖于虚拟方法分派。对于这种情况,请使用 shim 类型,如使用 shim 将您的应用程序与其他程序集隔离以进行单元测试中所述

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-05-30
              • 2010-12-17
              • 2011-12-08
              • 1970-01-01
              • 1970-01-01
              • 2014-12-27
              • 1970-01-01
              • 2010-12-18
              相关资源
              最近更新 更多