我是否应该建议默认密封类?

在我工作的一个大项目中,我正在考虑建议其他程序员,如果他们没有考虑如何子类化他们的类,就应该始终封闭他们的类。经验不足的程序员通常不会考虑这一点。

我发现 Java 和 C # 中的类在默认情况下是非密封的/非最终的,这很奇怪。我认为使类密封大大提高了代码的可读性。

请注意,这是内部代码,我们总是可以更改,如果发生罕见的情况下,我们需要子类。

你有什么经历?我对这个想法遇到了相当大的阻力。人们是不是懒到连键入 sealed都懒得?

7858 次浏览

我的观点是,架构设计决策是为了与其他开发人员(包括未来的维护开发人员)交流一些重要的东西。

密封类通知不应重写实现。它通知类不应该被模拟。我们有充分的理由封锁。

如果您采用不寻常的方法来密封所有内容(这是不寻常的) ,那么您的设计决策现在传达的内容实际上并不重要——比如类不是由原始/编写开发人员继承的 打算

但是,如何与其他开发人员沟通,让他们知道因为某些原因 应该类没有被继承呢?真的不行。你被困住了。

而且,封装类不会提高可读性。我看不出来。如果继承是 OOP 开发中的一个问题,那么我们就有一个更大的问题。

根据我的经验,我发现密封/最终类实际上非常罕见; 我当然不建议建议默认情况下所有类都是密封/最终类。该规范对代码的状态(例如,它是完整的)做出了某种声明,而这种声明在开发期间并不一定总是正确的。

我还要补充的是,让一个类保持不密封需要更多的设计/测试工作,以确保暴露的行为是良好的定义和测试; 重单元测试是至关重要的,国际海事组织,以实现对类的行为的信心水平,似乎是“密封”的支持者所希望的。但是,国际海事组织,这种增加的努力水平直接转化为高水平的信心和更高的质量代码。

坦率地说,我认为在 c # 中没有默认密封的类有点奇怪,而且与语言中其他默认类的工作方式不相称。

默认情况下,类是 internal。 默认情况下,字段是 private。 默认情况下,成员是 private

有一种趋势似乎表明,默认情况下最不可信的接入方式。理所当然地,unsealed关键字应该存在于 c # 而不是 sealed中。

就我个人而言,我更希望类是默认密封的。在大多数情况下,当某人编写一个类时,他在设计时并没有考虑子类化以及随之而来的所有复杂性。设计未来的子类应该是一个有意识的行为,因此我宁愿你明确地陈述它。

我愿意认为自己是一个经验丰富的程序员,如果我没有学到其他东西,那就是我在预测未来方面非常糟糕。

输入密封并不困难,我只是不想激怒一个开发商的道路(谁可能是我!)他发现一个问题可以很容易地用一点继承来解决。

我也不知道密封类如何使其更具可读性。你是不是想强迫人们选择合成而不是继承?

我不喜欢这样想。Java 和 c # 是面向对象的语言。这些语言是按照类可以有父类或子类的方式设计的。就是这样。

有些人说,我们应该始终从最具限制性的修饰符(private、 protected...)开始,只有在外部使用时才将您的成员设置为 public。对我来说,这些人很懒惰,不愿意在项目开始的时候考虑一个好的设计。

我的答案是: 现在就好好设计你的应用程序。在需要密封时将类设置为密封,在需要私有时将类设置为私有。不要让他们默认密封。

密封类的主要用途是去除用户的继承特性,使用户无法从密封类派生类。你确定要这么做吗。还是希望开始将所有类都密封起来,然后当需要使其可继承时,将对其进行更改。.当所有的东西都在一个团队内部时,这样也许没问题,但是如果将来其他团队使用你的 dls,那么在每次需要解密一个类时,就不可能重新编译整个源代码了... ..。 我不建议这样做,但这只是我的意见

如果我正在开发一个可重用的组件,并且我不希望最终用户继承它,或者作为一个系统架构师,如果我知道我不希望团队中的其他开发人员继承它,那么我只会密封类。然而,这通常是有原因的。

只是因为没有从中继承类,所以我不认为它应该被自动标记为密封。而且,当我想做一些棘手的事情的时候,它会让我烦恼不已。NET,但随后实现微软标记吨自己的类密封。

这是一个非常固执己见的问题,可能会得到一些非常固执己见的答案; -)

也就是说,在我看来,我强烈希望我的课程不要封闭/期末,特别是在开始的时候。这样做会使推断预期的扩展点变得非常困难,而且几乎不可能在一开始就得到正确的扩展点。恕我直言,过度使用封装比过度使用多态性更糟糕。

从类继承不应该有任何错误。

只有在有充分的理由说明为什么不应该继承类时,才应该密封该类。

此外,如果你把它们都密封起来,只会降低可维护性。每次有人想从你的类继承,他就会看到它是密封的,然后他要么移除密封(搞乱他不应该弄乱的代码) ,要么更糟: 为自己创建一个糟糕的类实现。

然后,您将有两个相同事物的实现,其中一个可能比另一个更糟糕,以及需要维护的两段代码。

最好不要封锁,不封锁也无妨。

好吧,就像其他很多人一样。

是的,我认为建议默认密封类是完全合理的。

这与 Josh Bloch 在他的 好极了有效的爪哇,第二版中的建议是一致的:

为继承而设计,或者禁止继承。

继承的设计是 用力,它可以使实现 更少变得灵活,特别是如果您有虚方法(其中一个调用另一个)。也许他们超载了,也许没有。事实上,一个方法调用另一个 必须记录在案,否则你不能安全地重写这两个方法——你不知道它什么时候被调用,或者你是否安全地调用另一个方法而不会冒堆栈溢出的风险。

现在,如果您以后想要更改以后版本中的哪个方法调用,您就不能-您可能会破坏子类。因此,在“灵活性”的名义下,您实际上已经使实现 更少变得灵活,并且必须更密切地记录实现细节。我觉得这不是个好主意。

接下来是不可变性——我喜欢不可变的类型。我发现它们比可变类型更容易推理。这是 乔达时间API 比在 Java 中使用 DateCalendar更好的原因之一。但是一个未密封的类永远不可能是 已知不可变的。如果我接受一个类型为 Foo的参数,我可能能够依赖于属性 Foo声明不会随着时间的推移而改变,但是我不能依赖于对象本身不会被修改——在子类中可能存在一个可变的属性。如果那个属性也被某个虚方法的重写所使用,那就只能听天由命了。向不变性的许多好处挥手告别。(具有讽刺意味的是,Joda Time 具有非常大的继承层次结构——通常有这样的内容: “子类应该是不可变的。Chronology的大型继承层次结构使得移植到 C # 时很难理解。)

最后,还有过度使用继承的方面。就我个人而言,在可行的情况下,我更喜欢组合而不是继承。我喜欢接口的多态性,而 偶尔我使用实现的继承性——但它很少适合我的经验。使类密封可以避免它们成为从哪里派生出来的 不恰当地,从哪里组合将是一个更好的选择。

编辑: 我还想指出 埃里克 · 利伯特2004年的博客文章的读者,为什么这么多的框架类是密封的。我想去的地方多了去了。NET 提供了一个 interface,我们可以工作的可测试性,但这是一个稍微不同的要求..。

你的地盘,你说了算。

您还可以使用补充规则: 一个可以被子类化的类必须被注释; 没有人应该对一个没有被注释的类进行子类化。遵守这条规则并不比遵守你的规则更难。

“ ... ... 考虑如何对他们的类进行子分类... ...”应该无关紧要。

在过去的几年里,我至少有六次发现自己在诅咒一些开源团队或者其他团队在保护和私有方面的草率混合,使得简单地扩展一个类而不复制整个父类的源代码变得不可能。(在大多数情况下,重写特定方法需要访问私有成员。)

一个例子是 JSTL 标记,它几乎完成了我想要的任务。我需要重写一个小东西。没有,抱歉,我完全抄袭了父母的信息来源。

杰弗里 · 里克特

有三个原因 类比一个未密封的 班级:

  • Versioning : 当一个类最初被密封时,它可以更改为 在未来解封 破坏兼容性。然而,有一次 一个类是解密的,你永远不能 把它改成未来密封的 这会破坏所有的派生类。 此外,如果解密的类 定义任何未密封的虚拟方法, 虚方法调用的顺序 必须使用新版本进行维护 否则就有可能破坏 派生类型。
  • Performance : 正如前一节所讨论的,调用虚拟 方法的性能不如 调用非虚方法是因为 CLR 必须查找 对象,以便在运行时 确定哪种类型定义了 但是,如果 JIT 编译器看到对虚拟 方法,使用密封类型 JIT 编译器可以产生更高的效率 通过调用方法来编写代码 非虚拟的。它能做到这一点是因为 它知道不可能有 如果类是密封的,则派生类。
  • 安全性 : 和可预测性 A 类必须保护自己的状态,而不是 让自己腐化堕落。 当类被解密时,派生的 类可以访问和操作 基类的状态(如果有数据字段) 或者内部操纵的方法 字段是可访问的,而不是私有的。 此外,虚方法可以是 被派生类重写,并且 派生类可以决定是否 调用基类的实现。 通过创建方法、属性或事件 虚拟,基类正在放弃 一些控制它的行为和它的 除非经过深思熟虑, 这会导致对象的行为 不可预测,它打开了 潜在的安全漏洞。