为什么在.Net中存在“sealed”关键字?

16

在 .Net 框架中,许多类都被标记为 "sealed",这意味着您无法使用自己的类继承这些类。这显然违反了面向对象编程的本质,即您可以扩展和重新定义现有对象的行为。

那么,'sealed' 关键字存在的好处是什么呢?

以 Silverlight 中的 NotifyCollectionChangedEventArgs 为例,它是被 sealed 的。我想创建一个支持 AddRange 和 RemoveRange 的 ObservableCollection 版本,但 Silverlight 版本的 NCCEA 不提供支持 NewItems 和 OldItems 属性的多个项的构造函数,这些属性已经定义为 ILists。通常,我会使用自己的变体扩展该类,覆盖 NewItems 和 OldItems 属性,但在这种情况下,我不能这样做,也看不出为什么会这样。


5
你或许想阅读Eric Lippert关于为什么.NET框架中的大多数内容都是sealed的博客文章:http://blogs.msdn.com/ericlippert/archive/2004/01/22/61803.aspx - gix
因为Java有final类修饰符。 - Austin_Anderson
8个回答

25

设计可扩展的类(或框架)并不是一件简单的事情,简单地说,继承并不是面向对象编程的唯一原则。

因此,sealed关键字的存在允许开发者/设计者表达和保留这些意图。将一个类标记为sealed可以通过减轻维护负担来使开发人员的生活更加容易。它允许原始开发者控制如何扩展类(或框架),从而可以在不担心破坏其他代码的情况下进行内部更改。

其中一个原则是,开发者应默认将任何叶子类标记为sealed。然后,当开发者有意创建一个未密封的类时,这将强制他们考虑可扩展性。


参考:Eric Lippert - 为什么许多框架类都被密封?


1
我觉得我在这里读错了什么。我看到你说它是为了保留框架开发者的意图,而且我们应该默认封装任何类以简化本地开发。这似乎与第一个想法相矛盾,因为如果封装所有类,那么使用您的框架的人将会遇到很多麻烦。否则,如果我们认为需要扩展封装的类,我们该怎么办?因此,如果您能给出一个好的实际示例,说明何时以及为什么应该使用它,那可能会有所帮助。可以吗? :-) - cregox
1
@Cawas,我真的只是主张在设计和思考之后才提供继承扩展点。理想情况下,一个更简单的代码库将通过减少长期维护成本来使您的生活更轻松。 - Robert Paulson
1
嗯,那时候我想理解,没有任何背景,问题上询问的完全相同的东西。 *为什么存在sealed*。你的回答对我来说并不够。现在我可以看出你的观点了。而我错过的都在细节中......让我提一个编辑建议。;-) - cregox
@Cawas 我可能会撤销其中一些编辑。使用sealed关键字是为了每个开发人员,因为实际上,每个人都创建自己的框架。 - Robert Paulson
加油,罗伯特!我没有注意到我改变了你的意图。 - cregox

7

这是我今天提出的一个有些相关问题的答案,它可能有助于澄清封装类的目的:

在开始开发自己的可重用库之前,我曾问过自己同样的问题。很多时候,你会遇到某些类,如果没有实现者进行晦涩或深奥的调用序列,就不能扩展该类。

当允许扩展你的类时,你必须考虑:如果开发人员扩展了我的类,并将这个新类传递给我的库,我能够透明地使用这个新类吗?我能正常地与这个新类一起工作吗?这个新类真的会表现得一模一样吗?

我发现,在.NET框架中,大多数密封类都有一些你不知道的底层要求,而且根据当前的实现,不能安全地暴露给子类使用。

里氏替换原则和组合

现在请点击链接并点赞实际作者。


2
简而言之,因为Microsoft这么说了。更长的答案是,Microsoft提供了一种称为扩展方法的机制来扩展密封类。
通常来说,扩展你没有源代码的类是一个坏主意。比如说,你不知道调用基方法对对象内部数据会有什么影响。当然,你可以使用反编译工具来弄清楚它,但通常最好使用组合或扩展方法。
你还必须考虑继承实际上是什么。它不仅是改变类的一种方式,它还提供了多态性。如果你改变了,比如,字符串类的语义,那么你将自己的新字符串类传递给需要特定行为的字符串对象,该怎么办?sealed本质上强制执行合同,保证这个对象始终按照你期望的方式工作。

但是,如果我有一个扩展字符串的新SuperString类,并且它的行为不符合其他类的期望,那么作为SuperString类的实现者,这肯定是我的问题。仅仅因为另一个开发人员可能会破坏它,就将一个类封装起来似乎很愚蠢。 - X-Cubed
如果您只在自己的代码中使用字符串,那就没问题了。但是像String这样的类在整个框架中都被使用。微软希望确保它们不会因为自己的使用而改变。 - Erik Funkenbusch

2
不深入探讨,了解Microsoft在处理下游潜在的安全性、可维护性或向后兼容性问题时更喜欢封装类。例如,System.String出于安全性和性能原因而被封装。
在这种特定情况下,您需要询问Microsoft开发人员为什么选择封装该类。然而,我最近阅读的架构指南文献倾向于采用“除非您知道它需要扩展,否则请封装”方法。这些文献倾向于使用扩展方法(我不是说我同意,我只是说这是我最近阅读的内容)。
即使该类未被封装,所涉及的属性可能仍未被设置为虚拟的,这仍将使您陷入困境。
在您的具体情况下,我建议使用自己独特的名称的扩展方法。这是你能做的全部。

1

虽然我同意.NET可能封装了太多,但这通常是为了保护生态系统的完整性。当你的首要任务之一是保持整体框架/API/运行时的稳定性,而类之间存在相对脆弱的依赖关系时,防止人们覆盖该行为并无意中破坏核心功能可能是最安全的。

不过,我还是觉得.NET团队封装了太多的类。有时候封装会是开发人员简单懒惰的表现,因为适当的类设计会涉及到太多的工作。


1

我认为除了保护无知的人(呃,我是说无辜的人)之外,没有什么好的理由来封装类。你知道那句老话,“给他们足够的绳子,他们就会自己上吊”。让他们自己摇摆吧。也许这是我的C++背景,但我很放心,知道如果我不勤奋的话,我有完全搞砸事情的能力。

我倾向于通过接口进行编程。接口当然是公共的,任何人都可以提供自己的实现,以符合接口所表达的契约。实现这些接口的具体类往往是私人关注,并标记为internal和/或private。我觉得我不需要封装这样的类。

在需要代码重用的地方,我避免使用继承进行重用,而更喜欢组合和其他技术。

封装也可能适用于被认为是普通数据类型的类型,但我对此并不确定。


0

这要看情况而定,有些类只是用来实例化的(如果存在继承,那么继承只是为了简化实现),而其他类则旨在被继承以提供特定的实现。

密封类有一些优点:

  • 它们没有任何虚方法,因此不必担心非“异常安全”的实现重写方法。
  • 如果一个类是不可变的,它可以保持和保证不可变性。

否则,如果您想使用“舒适”方法装饰一个密封类,请使用扩展方法(C# 3.0)。


0

仅仅因为某个东西是一个对象,并不意味着它总是应该为每个人开放以扩展或重新定义其行为。

一个很好的比喻就是门和锁。门的本质是让人们通过,这就是它的设计目的。然而,大多数门都设计有锁,可以限制人们通过的能力。是否给门加锁以及默认是否上锁的决定留给房间的建筑师,基于房间内的物品和谁应该有权访问它,而不是基于它是一扇门这个事实。

当然,如果特定建筑物中的大多数门默认上锁,尤其是你真的想通过其中的一扇门时,这可能会让人感到沮丧。:-) 我自己也曾经历过这种情况,并提出了同样的问题。简短的答案是,在设计复杂框架时,有时人们倾向于更加谨慎,将类默认封闭,除非有明确的场景需要对其进行扩展。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接