我应该默认推荐密封类吗

本文关键字:密封类 默认 我应该 | 更新日期: 2023-09-27 17:59:20

在我工作的一个大项目中,我正在考虑建议其他程序员,如果他们没有考虑如何将自己的类子类化,就总是密封他们的类。通常情况下,经验不足的程序员从未考虑过这一点。

我觉得奇怪的是,在Java和C#中,类默认情况下是非密封的/非最终的。我认为将类密封可以极大地提高代码的可读性。

请注意,这是内部代码,如果出现我们需要子类化的罕见情况,我们可以随时更改。

你的经历是什么?我对这个想法遇到了一些阻力。人们是不是懒惰到连键入sealed都懒得打?

我应该默认推荐密封类吗

好吧,因为很多其他人都参与了…

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

这与Josh Bloch在其优秀书《高效Java》第二版中的建议一致:

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

为继承进行设计是很困难的,并且可以降低实现的灵活性,特别是如果您有虚拟方法,其中一个方法调用另一个方法。也许它们是过载,也许它们不是。必须记录其中一个调用另一个的事实,否则您无法安全地重写其中一个方法-您不知道何时调用它,也不知道在不冒堆栈溢出风险的情况下调用另一方法是否安全。

现在,如果您以后想在以后的版本中更改哪个方法调用哪个,您就不能这样做——您可能会破坏子类。因此,以"灵活性"的名义,您实际上降低了实现的灵活性,并且必须更紧密地记录您的实现细节。这对我来说不是个好主意。

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

最后,还有过度使用继承的问题。就我个人而言,在可行的情况下,我更喜欢组合而不是继承。我喜欢接口的多态性,偶尔我会使用实现的继承,但这很少适合我的经验。使类密封可以避免它们被不适当地从更适合组合的地方派生出来。

编辑:我还想向读者指出Eric Lippert在2004年的博客文章,为什么这么多框架类都是密封的。有很多地方我希望.NET提供一个接口,我们可以在那里进行测试,但这是一个略有不同的请求。。。

我认为,架构设计决策是为了与其他开发人员(包括未来的维护开发人员)沟通一些重要的事情。

密封类表示不应重写实现。它表示不应模拟该类。密封是有充分理由的。

如果你采取了不寻常的方法来密封所有东西(这是不寻常的),那么你的设计决策现在传达的是真正不重要的东西——比如这个类不是打算由原始/创作开发人员继承的。

但是,您将如何与其他开发人员沟通,即类不应该因为某些原因而被继承?你真的做不到。你被卡住了。

此外,密封类并不能提高可读性。我就是不明白。如果继承是OOP开发中的一个问题,那么我们就有一个更大的问题。

我想我是一个经验丰富的程序员,如果我什么都没学到,那就是我在预测未来方面非常糟糕。

键入sealed并不难,我只是不想激怒未来的开发人员(可能是我!),他们发现只需一点继承就可以轻松解决问题。

我也不知道如何密封一个类使它更可读。你是想强迫人们更喜欢组合而不是继承吗?

©Jeffrey Richter

密封有三个原因上课总比不上课好类别:

  • 版本控制:当一个类最初被密封时,它可以更改为在没有破坏兼容性。然而,有一次一个班级是不公开的,你永远不能将来将其更改为密封这将破坏所有派生类。此外,如果未密封的类定义任何未密封的虚拟方法,虚拟方法调用的排序必须使用新版本进行维护或者有破裂的可能未来的派生类型
  • 性能:如前一节所述,调用虚拟方法的性能不如调用非虚拟方法是因为CLR必须查找对象,以便确定哪种类型定义方法调用。但是,如果JIT编译器看到对虚拟方法使用密封类型JIT编译器可以产生更高效的通过调用方法编写代码非虚拟的。它能做到这一点是因为它知道不可能有派生类(如果该类是密封的)
  • 安全性:和可预测性类必须保护自己的状态,而不是允许自己变得堕落。当一个类被解封时,派生的类可以访问和操作基类的状态(如果有数据字段)或内部操纵的方法字段是可访问的,而不是私有的。此外,虚拟方法可以被派生类重写,并且派生类可以决定是否调用基类的实现。通过生成方法、属性或事件虚拟的,基类正在放弃对其行为及其状态除非仔细考虑,这会导致对象行为不可预测地,它打开了潜在的安全漏洞

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

只有在有充分理由永远不应该继承类的情况下,才应该封存该类。

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

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

最好保持密封。打开它没有害处。

坦率地说,我认为c#中默认情况下没有密封的类有点奇怪,与语言中其他默认值的工作方式格格不入。

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

似乎有一种趋势指向默认情况下最不可信的访问。unsealed关键字应该出现在c#中而不是sealed中,这是理所当然的。

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

"…考虑一下他们的类应该如何被子类化…"应该无关紧要。

在过去的几年里,我发现自己至少有六次咒骂某个开源团队,因为他们草率地将受保护的和私有的混合在一起,使得在不复制整个父类的源代码的情况下简单地扩展一个类是不可能的。(在大多数情况下,覆盖特定方法需要访问私人成员。)

一个例子是一个JSTL标签,它几乎做到了我想要的。我需要推翻一件小事。没有,对不起,我不得不完全复制家长的来源。

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

仅仅因为类不是从继承的,我不认为它应该自动标记为密封的。此外,当我想做一些棘手的事情时,这会让我恼火不已。NET,但随后意识到MS标记了大量自己的类被密封。

这是一个非常有主见的问题,可能会得到一些非常有主观主义的答案;-)

也就是说,在我看来,我强烈希望不要把我的课程封为最终课程,尤其是在刚开始的时候。这样做会使推断预期的可扩展性点变得非常困难,而且几乎不可能在一开始就正确地获取它们。IMHO,过度使用封装比过度使用多态更糟糕。

你的房子,你的规则。

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

密封类的主要目的是剥夺用户的继承功能,使他们无法从密封类派生类。你确定要那样做吗。或者,你想开始将所有类都密封起来,然后当你需要使其可继承时,你会更改它。。好吧,当每件事都在内部和一个团队中时,这可能是可以的,但如果其他团队将来使用您的dll,则不可能每次需要解封类时都重新编译整个源代码。。。。我不建议这样做,但这只是我的观点

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

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

我的答案是:现在就用一种好的方式设计你的应用程序。将你的类设置为需要密封时密封,需要私有时私有。不要在默认情况下将它们密封。

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

我还要补充一点,让一个类不密封需要更多的设计/测试工作,以确保暴露的行为得到定义和测试;IMO认为,重型单元测试至关重要,以达到"密封"支持者所期望的对该类行为的信心水平。但是,国际海事组织的努力水平的提高直接转化为高水平的信心和更高质量的代码。