为什么默认情况下不将类标记为密封的?

38

我只是想知道,既然sealed关键字的存在表明这是类作者决定其他类是否允许从它继承的决定,为什么不将类默认设置为sealed,必要时使用关键字标记为可扩展呢?

虽然访问修饰符略有不同,但也是采用了这种方式。默认情况下访问受限,只有在插入关键字后才能获得更多的访问权限。

虽然我很可能没有认真思考过这个问题,所以请多包容!


4
我认为,如果您默认将类设为密封类,则不应该使用面向对象的语言。 - Jon Limjap
我在.NET Framework中遇到了太多次我想要继承和覆盖功能的密封类。内部也是如此。 - Chris Pietschmann
一个具有对象和消息但没有继承的语言不是面向对象的,而只是基于对象的。 - Steven A. Lowe
1
@Chris:如果你仍然对封闭的.NET Framework类感到不满意,你可能会发现http://blogs.msdn.com/b/ericlippert/archive/2004/01/22/61803.aspx是一篇有趣的阅读材料。 - Brian
从MSDN:要确定是否密封类、方法或属性,通常应考虑以下两个要点: 1)派生类可能通过自定义您的类获得的潜在好处。2)派生类可能会修改您的类,导致它们无法正确运行或按预期工作的潜力。...... 我的偏好是,如果我作为一个类的作者允许别人从它派生不需要额外的工作,我就不标记它为密封的。但如果我的类不打算以某种微妙的方式进行派生,则将其密封。 - zumalifeguard
这位 C# 团队的前成员提供了一个有趣的观点 - 他希望他们将 sealed 设为默认选项:https://dev59.com/PHI95IYBdhLWcg3wxA4- - niico
9个回答

55

我认为那只是个错误。我知道很多人(包括我自己)认为类应该默认为sealed。C#设计团队中至少有几个人持这种观点。自从C#首次设计以来,继承已经逐渐被较少使用了。(当然,它也有其适用的地方,但我发现我相对较少使用它。)

说句题外话,这并不是太接近Java的唯一错误:就我个人而言,我宁愿Equals和GetHashCode不在object里面,并且锁定时需要特定的Monitor实例。


1
如果 GetHashCode 不在 object 中,那么每个聚合器(甚至不是继承者)都必须被赋予访问每种类型的私有成员的权限,并且聚合器必须为每种类型编写自己的哈希函数。你真的真的认为这会减少编程中的痛苦吗? - Windows programmer
10
@Windows:两种情况都不会出现。您可以选择在适当的情况下实现相等性和GetHashCode,并实现一个接口。可以包含一个系统IEqualityComparer来执行身份比较。其他比较可以基于公共属性。这样做没问题。 注:本翻译旨在使原文更易懂,但并不改变其含义。 - Jon Skeet
12
C#的三个主要错误:默认可变、默认未封闭、默认可为空。 - Den
@学习-过度思考者-困惑:你指的是博客文章中的哪一行?你所说的“用于继承”和“参与继承”是什么意思?(每个类都参与继承,至少从System.Object继承。)我认为你可能误解了这篇文章。Stephen在结尾写道,他会将每个类都标记为sealed,除非“它真正需要成为一个基类”。 - Jon Skeet
好的,假设我有一个名为Product的类(独立类),并且没有任何继承自该Product类的类,这个原因足以将Product类标记为sealed吗? - Learning-Overthinker-Confused
显示剩余6条评论

32

我认为,不应有默认语法,这样你总是要明确写出你想要的东西。这迫使编码者更加理解思考。

如果您希望一个类可以继承,则需要编写:

public extensible class MyClass

否则

public sealed class MyClass

顺便说一句,我认为访问修饰符也应该如此,禁止使用默认的访问修饰符。


好的观点,你必须始终清楚自己在做什么,而不是有意或无意地依赖不可见的默认设置。 - xyz
你可以考虑使用抽象修饰符来实现继承的目的。 - Spoike
如果没有提供关键字会发生什么?只有 public class MyClass - cregox
1
我理解你的意思 - 尽管这样做可能会变得非常冗长。为开发人员做出明智的默认、常规决策似乎是合理的做法... - niico

12

继承是面向对象编程的基本原则,因此,从默认情况下禁止它可能并不直观。


39
许多人会认为,大多数类并没有被设计成可被继承的。 - Michael Burr
14
我认为大多数开发者在构建类时并没有考虑继承。 - Steve Horn
2
Mike和Steve,如果你们遵循良好的面向对象编程规范,那么你们将设计可被继承和重用的类。 - Chris Pietschmann
36
@Chris: 没门。大多数类都不会被派生,猜测哪些元素需要特殊化不仅浪费时间,还会在未来的实现决策中建立限制。请参考《Effective Java》中对此的良好讨论。 - Jon Skeet
6
如果我可以给Jon在这里的评论点赞,我会这么做 - 这是基本观点 - 为继承设计确实需要很多深思熟虑,而且大多数情况下都是不必要的 - 那么为什么默认要使用一些需要开发人员浪费脑力的东西呢? - Phil

4

对于默认启用密封机制和默认禁用密封机制各有利弊。如果情况相反,那么就会有人发表相反的观点。


3

3

sealed classes(密封类)禁止继承,因此是面向对象编程中的一种反模式。详见此篇文章


1
谢谢你们毫无理由地进行负面投票 - 懦夫!:-P - Steven A. Lowe
3
我认为实现继承在重要性上被夸大了。我通常会同意默认应该是密封的,因为使用继承非常容易出错。如果可行的话,应该使用组合代替继承。所以,我非常不赞同你的观点 :)对我来说,面向对象开发的主要优势是接口和多态性。 - Phil
2
@[Phil]:所以,每次创建Windows表单时,您都会封装一个Form对象而不是继承自System.Windows.Form吗?看起来需要做很多额外的工作... 继承是基础;学习它,生活中实践它,热爱它;-) - Steven A. Lowe
1
@StevenA.Lowe 为了记录,我应该指出 Form 是设计用于扩展的,这也是这场辩论的全部意义。你和我都认为,应该努力设计可扩展的代码,然而经验证明,太多人不这样做。因此,将一个类密封或不密封在理论上没有区别;那些类不能被扩展,因为它们没有被设计成可扩展的,就是这样。唯一的实际区别是,在一个情况下是显而易见和明确(密封),而在另一个情况下,当它在运行时崩溃时你会发现它。 - tne

3
仅仅从一个未封装的类中派生并不会改变该类的行为。最糟糕的情况是基类的新版本将添加与派生类同名的成员(此时只会出现编译器警告,提示您应使用“new”或“override”修饰符),或者基类是已发布到外部的设计不良封闭类。任意子类仍然符合Liskov替换原则
在C#中默认情况下不允许重写成员的原因是重写方法可能会以基类作者没有预期的方式更改基类的行为。通过显式声明抽象或虚方法,表示作者知道它可能会发生变化或者无法控制,并且作者应该考虑到这一点。

3

Word 的 80% 功能被闲置了。 80% 的类没有被继承。 在这两种情况下,间或会有人想使用或重复使用某个功能。 为什么原始设计者要禁止重复使用呢? 让重复使用者自己决定他们想要重复使用什么。


3
这是反对sealed关键字存在的一个论据,而不是默认值 :) - xyz

0
出于与对象类比的一致性考虑,对象默认情况下不是私有的,同样的原因也适用于此。
只是猜测,因为归根结底这是一种语言设计决策,创作者所说的才是正统材料。

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