我应该推荐默认的密封类吗?

Car*_*ngo 65 .net c# java oop

在我工作的一个大项目中,我正在考虑建议其他程序员如果没有考虑他们的课程应该如何分类,他们总是密封他们的课程.很少有经验的程序员从不考虑这一点.

我发现奇怪的是,在java和c #classd中是非密封/非最终的pr默认值.我认为密封课程大大提高了代码的可读性.

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

你有什么经历?我对这个想法遇到了很多阻力.是懒惰的人,他们不能打扰"密封"吗?

Jon*_*eet 62

好吧,正如许多其他人所说的......

是的,我认为建议默认密封课程是完全合理的.

这与Josh Bloch在他的优秀书籍Effective Java,第2版中的推荐一致:

设计继承,或禁止它.

设计继承很困难,并且可能使您的实现不那么灵活,特别是如果您有虚拟方法,其中一个调用另一个.也许他们是超载,也许他们不是.一个人调用另一个的事实必须记录在案,否则你不能安全地覆盖任何一种方法 - 你不知道它何时被调用,或者你是否可以安全地调用另一种方法而不会有堆栈溢出的风险.

现在,如果您以后想要更改哪个方法在更高版本中调用,则不能 - 您可能会破坏子类.因此,以"灵活性"的名义,您实际上使实现变得不那么灵活,并且必须更密切地记录您的实现细节.这对我来说听起来不是一个好主意.

接下来是不变性 - 我喜欢不可变类型.我发现它们比可变类型更容易推理.这是一个原因,为什么约达时间 API比使用更好Date,并Calendar在Java中.但是,一个未密封的类永远不会被认为是不可变的.如果我接受一个类型的参数Foo,我可能依赖于声明Foo的属性不会随着时间的推移而改变,但我不能依赖于对象本身没有被修改 - 在子类中可能有一个可变属性.如果某个虚拟方法的覆盖也使用了该属性,则Heaven帮助我.挥手告别不变性的许多好处.(具有讽刺意味的是,Joda Time拥有非常大的继承层次结构 - 通常会说"子类应该是不可变的.大型继承层次结构Chronology使得在移植到C#时很难理解."

最后,还有过度使用继承的方面.就个人而言,在可行的情 我喜欢接口的多态性,有时我会使用继承的实现 - 但它很少适合我的经验.密封课程可以避免不适当地从合成更合适的地方获得.

编辑:我还想向读者指出Eric Lippert在2004发表的关于为什么这么多框架类被密封的博客文章.有很多地方我希望.NET提供一个我们可以用来测试的接口,但这是一个稍微不同的请求......

  • Bloch的建议通常不适用于大多数开发人员.大多数人不会向持续数十年的数百万用户发布公共API.与OP一样,它们在内部工作,API演变的经济学完全不同.向后不兼容的更改不仅是可能的,也是允许的,它们是日常活动. (7认同)
  • @irreputable:当然,类不必在公司外部公开,以便成千上万原始开发人员不知道的人广泛使用.许多开发人员在拥有大型内部系统的大型机构中工作.我认为尝试设计一个API是合理的,但*不要*尝试设计它时考虑到每个可能的子类化方案. (4认同)
  • @Paul:这绝对不应该掉以轻心.但至少它是可能的.我一般同意,如果*有*破坏,导致立即失败是好的. (2认同)

Jer*_*xon 20

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

密封类传达不应覆盖实现.它表示不应该冒充该类.密封是有充分理由的.

如果你采取不同寻常的方法来封装所有东西(这是不寻常的),那么你的设计决策现在可以传达非常重要的东西 - 就像那个类不打算由原始/创作开发者继承.

但随后你将如何传达给其他开发类应该不是因为什么被继承?你真的不能.你被卡住了

而且,密封课程并不会提高可读性.我只是没有看到.如果继承是OOP开发中的问题,那么我们就会遇到更大的问题.

  • 如果一个类是密封的,那么使用它的代码更具可读性,因为你不必考虑语义上的任何变化.你知道它的表现会更简单. (16认同)
  • 我们将不得不同意不同意@JonSkeet.密封的类本身可能具有很长的继承性.密封它不包含复杂性.事实上,密封它对可读性没有任何作用.这就像你将Sealed称为"锁定"或其他东西.密封只会阻止继承. (6认同)
  • @Paul:那我们不同意:)我知道我在使用密封课程时会更舒服.然后,我也更习惯使用不可变类 - 并且一个未密封的类不能保证不变性,要么...... (5认同)
  • @JonSkeet:我认为可读性的改进确实来自隐含的语义不会改变的保证; 然而,在OP提出的情况下,密封的默认使用完全失败了暗示保证,因此失去了可读性的好处. (4认同)
  • @JonSkeet:不要在评论中对此讨论过于深入,但我发现详尽的单元测试通过明确定义代码所期望的合同来给我这种保证. (3认同)
  • @Jerry:我不知道你对"Locked"的意思,密封类本身有一个很长的继承层次也没关系 - 我知道没有别的东西可以覆盖它的行为. (3认同)

48k*_*ocs 9

我想我是一个经验丰富的程序员,如果我没有学到任何东西,那就是我在预测未来方面非常糟糕.

打字密封并不难,我只是不想激怒开发人员(可能是我!)谁发现问题可以通过一点继承轻松解决.

我也不知道密封类如何使它更具可读性.你是否试图强迫人们更喜欢作曲继承?

  • 你是对的,我们无法预测未来 - 那么为什么不选择限制最少的选择,就未来的变化而言?一旦你开始上课并且有人从中获得了,你就无法改变这个决定.如果你想在以后*开启*一个类,那可以在不破坏任何东西的情况下完成.在你知道需要它之前不要邀请子类 - 此时你会知道*为什么*你需要它,所以你可以设计继承的那个方面. (12认同)
  • 我不一定同意.如果您真的想要为继承添加一些值,则必须设计一个具有子类的类.如果你通过简单地使用'has a'关系对内部状态具有相同的访问权限,那么继承一个类有什么意义呢?前者需要使用受保护的状态和一组受保护的"工具",这意味着您必须考虑到子类化而设计类. (4认同)
  • @Jon Skeet:如果有人从我的班级继承,我必须希望他们有充分的理由这样做.就像我在我的回答中所说的那样,我很难看到未来,而且我可能不那么聪明.我没有问题想象有人可以通过从类继承来找到解决问题的方法.至于密封它直到那一点,我并不反对你,只是说点不足以改变我的编码习惯.你把它读作邀请子类,我把它读作允许它. (3认同)

Kir*_*huk 7

\xc2\xa9 杰弗里·里希特

\n\n
\n

密封类比未密封类更好的原因有以下三个:

\n\n
    \n
  • 版本控制:当一个类最初是密封的时,它可以在将来更改为未密封的,而不会破坏兼容性。但是,一旦\n类被解封,您将来就无法将其更改为\n密封,因为\n这会破坏所有派生类。\n此外,如果未密封的类\n定义了任何未密封的虚拟方法,\n必须在新版本中维护虚拟方法调用的顺序,否则将来可能会破坏派生类型。
  • \n
  • 性能:如上一节所述,调用虚拟方法的执行效果不如调用非虚拟方法,因为 CLR 必须在运行时查找对象的类型以便确定哪种类型定义了要调用的方法。但是,如果 JIT\n 编译器发现使用密封类型对虚拟\n 方法的调用,则 JIT\n 编译器可以通过非虚拟\n 调用该方法\n 来生成更高效的代码。它可以这样做是因为\n 它知道如果该类是密封的,则可能\xe2\x80\x99t 可能是派生类。
  • \n
  • 安全性:和可预测性 类必须保护自己的状态,并且不允许自己被损坏。当类解封时,派生类可以访问和操作基类xe2x80x99s说明任何数据字段或内部操作字段的方法是否可访问且不是私有的。此外,虚拟方法可以由派生类重写,并且派生类可以决定是否\n调用基类\xe2\x80\x99s 实现。\n 通过将方法、属性或事件设置为虚拟,\n 基类将放弃\n 对其行为及其状态的部分控制。除非经过深思熟虑,否则这可能会导致对象行为不可预测,并造成潜在的安全漏洞。
  • \n
\n
\n


InB*_*een 5

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

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

默认情况下,似乎有一种趋势指向最不合理的访问。一个unsealed关键字应该在 c# 中退出而不是sealed.

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


Yoc*_*mer 5

继承一个类应该没有任何错误.

你应该只在有一个很好的理由不应该继承的时候封一课.

此外,如果你全部密封,它只会降低可维护性.每当有人想从你的某个班级继承,他就会看到它是密封的,然后他要么取消印章(弄乱他不应该乱用的代码)或者更糟糕的是:创建一个糟糕的实施你的为自己上课.

那么你将有2个相同的实现,一个可能比另一个更糟糕,并且需要维护2个代码.

更好的是保持开封.没有密封它没有伤害.