9

阻止类被继承的原因是什么?(例如在ac# 类上使用sealed) 现在我想不出任何东西。

4

14 回答 14

22

因为编写可替代扩展的类非常困难,并且需要您准确预测未来用户将如何扩展您编写的内容。

密封你的班级会迫使他们使用组合,这更加健壮。

于 2008-08-23T21:40:45.023 回答
9

如果您还不确定接口并且不想要任何其他取决于当前接口的代码怎么办?[这不是我的想法,但我也会对其他原因感兴趣!]

编辑:一些谷歌搜索给出了以下内容: http ://codebetter.com/blogs/patricksmacchia/archive/2008/01/05/rambling-on-the-sealed-keyword.aspx

报价:

密封类优于非密封类的三个原因:

  • 版本控制:当一个类最初是密封的时,它可以在将来更改为未密封的,而不会破坏兼容性。(……)
  • 性能:(...)如果 JIT 编译器看到使用密封类型对虚拟方法的调用,JIT 编译器可以通过非虚拟方式调用该方法来生成更高效的代码。(...)
  • 安全性和可预测性:一个类必须保护自己的状态,不允许自己被破坏。当一个类被解封时,如果任何内部操作字段的数据字段或方法是可访问的而不是私有的,则派生类可以访问和操作基类的状态。(…)
于 2008-08-23T21:37:29.260 回答
8

我想从“代码完成”给你这个消息:

继承 - 子类 - 往往与您作为程序员的主要技术要求背道而驰,即管理复杂性。为了控制复杂性,您应该对继承保持强烈的偏见。

于 2008-08-23T21:56:07.687 回答
2

继承的唯一合法用途是定义基类的特定情况,例如,从 Shape 继承以派生 Circle。要检查这一点,请查看相反方向的关系:Shape 是 Circle 的概括吗?如果答案是肯定的,那么可以使用继承。

因此,如果您有一个不能有任何特殊情况专门针对其行为的类,则应将其密封。

同样由于 LSP(Liskov 替换原则),人们可以在需要基类的地方使用派生类,这实际上对使用继承产生了最大的影响:使用基类的代码可能会被赋予一个继承类,它仍然必须按预期工作. 为了在没有明显需要子类的情况下保护外部代码,您可以密封该类,并且它的客户可以依赖它的行为不会改变。否则,需要明确设计外部代码,以预期子类中的行为可能发生变化。

一个更具体的例子是单例模式。您需要密封单例以确保不能破坏“单例”。

于 2008-08-24T12:34:27.123 回答
1

从面向对象的角度来看,封装类清楚地记录了作者的意图,无需注释。当我密封一个课程时,我想说这个课程旨在封装一些特定的知识或一些特定的服务。它并不意味着进一步增强或细分。

这与模板方法设计模式相得益彰。我有一个界面显示“我执行此服务”。然后我有一个实现该接口的类。但是,如果执行该服务依赖于基类不知道(也不应该知道)的上下文怎么办?发生的情况是,基类提供了虚拟方法,它们要么是受保护的,要么是私有的,这些虚拟方法是子类的挂钩,用于提供基类不知道和不知道的信息或操作。同时,基类可以包含所有子类通用的代码。这些子类将被密封,因为它们旨在完成该服务的唯一一个具体实现。

你能提出这些子类应该被进一步子类化以增强它们的论点吗?我会说不,因为如果该子类一开始就无法完成工作,那么它就不应该从基类派生。如果你不喜欢它,那么你有原始接口,去编写你自己的实现类。

密封这些子类也阻碍了深层次的继承,这对 GUI 框架很有效,但对业务逻辑层效果不佳。

于 2008-09-05T01:26:00.720 回答
1

这可能不适用于您的代码,但 .NET 框架中的许多类是故意密封的,因此没有人试图创建子类。

在某些情况下,内部结构很复杂,需要非常具体地控制某些东西,因此设计者决定没有人应该继承该类,这样就不会有人以错误的方式使用某些东西而意外破坏功能。

于 2008-08-23T21:42:44.863 回答
1

@jjnguy

另一个用户可能希望通过对您的类进行子分类来重用您的代码。我看不出有什么理由阻止这一切。

如果他们想使用我的类的功能,他们可以通过包含来实现,因此他们的代码会少得多。

构图似乎经常被忽视;人们常常想加入继承的潮流。他们不应该!可替代性是困难的。默认组合;从长远来看,你会感谢我的。

于 2008-08-23T21:45:11.380 回答
1

我同意 jjnguy... 我认为封课的原因很少而且相差甚远。恰恰相反,我不止一次遇到过我想扩展一个类的情况,但因为它是密封的而不能。

作为一个完美的例子,我最近创建了一个小包(Java,不是 C#,但原理相同)来包装 memcached 工具的功能。我想要一个接口,所以在测试中我可以模拟我正在使用的 memcached 客户端 API,并且如果需要,我们可以切换客户端(memcached 主页上列出了 2 个客户端)。此外,如果需要或希望出现,我希望有机会完全替换该功能(例如,如果 memcached 服务器由于某种原因关闭,我们可能会使用本地缓存实现进行热交换)。

我公开了一个与客户端 API 交互的最小接口,扩展客户端 API 类然后在我的新接口中添加一个 implements 子句会很棒。我在接口中拥有的与实际接口匹配的方法将不需要进一步的细节,因此我不必显式地实现它们。然而,这个类是密封的,所以我不得不代理调用这个类的内部引用。结果:没有真正充分的理由,更多的工作和更多的代码。

就是说,我认为有时您可能希望密封一个类……而我能想到的最好的事情是您将直接调用的 API,但允许客户端实现。例如,您可以针对游戏进行编程的游戏……如果您的类没有被密封,那么添加功能的玩家可能会利用 API 来获得优势。虽然这是一个非常狭窄的案例,我认为任何时候你可以完全控制代码库,真的没有什么理由让一个类被密封。

这是我真正喜欢 Ruby 编程语言的原因之一……甚至核心类也是开放的,不仅是为了扩展,而且是为了动态地添加和更改功能,对类本身!它被称为猴子补丁,如果被滥用可能是一场噩梦,但玩起来非常有趣!

于 2008-08-23T21:57:11.973 回答
0

因为出于各种原因,您总是希望获得对类的引用而不是派生类的引用:
i。您在代码的其他部分中拥有的不变量
ii。安全

此外,因为就向后兼容性而言,这是一个安全的赌注——如果它未密封发布,您将永远无法关闭该类以进行继承。

或者您可能没有足够的时间来测试该类公开的接口以确保您可以允许其他人从它继承。

或者也许没有意义(你现在看到)拥有一个子类。

或者,当人们尝试子类化并且无法获得所有细节时,您不想要错误报告 - 削减支持成本。

于 2008-08-23T21:43:24.223 回答
0

有时你的类接口并不意味着被继承。公共接口不是虚拟的,虽然有人可以覆盖现有的功能,但它只是错误的。是的,通常它们不应该覆盖公共接口,但您可以通过使类不可继承来确保它们不会覆盖。

我现在能想到的示例是 .Net 中具有深度克隆的自定义包含类。如果你从它们那里继承,你就会失去深度克隆能力。[我对这个例子有点模糊,我已经有一段时间没有使用 IClonable] 如果你有一个真正的单例类,你可能不想要继承的形式它周围,数据持久层通常不是你想要大量继承的地方。

于 2008-08-23T21:45:29.240 回答
0

并非类中重要的所有内容都可以在代码中轻松断言。可能存在语义和关系,这些语义和关系很容易被继承和覆盖方法破坏。一次覆盖一个方法是一种简单的方法。您将一个类/对象设计为一个有意义的实体,然后有人出现并认为如果一两个方法“更好”它不会造成伤害。这可能是真的,也可能不是。也许您可以正确地将私有与非私有或虚拟与非虚拟之间的所有方法分开,但这可能还不够。要求所有类的继承也给原始开发人员带来了巨大的额外负担,以预见继承类可能搞砸的所有方式。
我不知道一个完美的解决方案。我很同情防止继承,但这也是一个问题,因为它阻碍了单元测试。

于 2008-08-23T22:01:43.553 回答
0

我公开了一个与客户端 API 交互的最小接口,扩展客户端 API 类然后在我的新接口中添加一个 implements 子句会很棒。我在接口中拥有的与实际接口匹配的方法将不需要进一步的细节,因此我不必显式地实现它们。然而,这个类是密封的,所以我不得不代理调用这个类的内部引用。结果:没有真正充分的理由,更多的工作和更多的代码。

好吧,这是有原因的:您的代码现在与 memcached 接口的更改有些隔离。

于 2008-08-23T22:13:44.867 回答
0

性能:(...)如果 JIT 编译器看到使用密封类型对虚拟方法的调用,则 JIT 编译器可以通过非虚拟方式调用该方法来生成更高效的代码。(...)

这确实是一个很好的理由。因此,对于性能要求高的班级,sealed和朋友们是有道理的。

到目前为止,我所看到的所有其他原因都归结为“没有人碰我的课!”。如果您担心有人可能会误解它的内部结构,那么您在记录它时做得很差。你不可能知道没有什么有用的东西可以添加到你的类中,或者你已经知道它的每个可以想象的用例。即使您是对的并且其他开发人员不应该使用您的类来解决他们的问题,使用关键字也不是防止此类错误的好方法。文档是。如果他们忽视文件,他们的损失。

于 2008-08-23T22:24:49.927 回答
0

大多数答案(抽象时)表明密封/最终类是保护其他程序员免受潜在错误影响的工具。有意义的保护和无意义的限制之间的界限很模糊。但只要程序员是被期望理解程序的人,我认为几乎没有任何理由限制他重用类的某些部分。你们中的大多数人都在谈论课程。但这都是关于对象的!

在他的第一篇文章中,DrPizza 声称设计可继承类意味着预测可能的扩展。我是否正确地认为您认为该类只有在它可能被很好地扩展的情况下才应该是可继承的?看起来你好像习惯于从最抽象的类中设计软件。请允许我简要解释一下我在设计时的想法:

从非常具体的对象开始,我找到了它们共同的特征和[因此]功能,并将其抽象为那些特定对象的超类。这是一种减少代码重复的方法。

除非开发一些特定的产品,例如框架,否则我应该关心我的代码,而不是其他(虚拟)代码。其他人可能会发现重用我的代码很有用这一事实是一个很好的奖励,而不是我的主要目标。如果他们决定这样做,他们有责任确保扩展的有效性。这适用于整个团队。前期设计对生产力至关重要。

回到我的想法:你的对象应该主要服务于你的目的,而不是它们子类型的一些可能的应该/将/可能功能。你的目标是解决给定的问题。面向对象的语言使用许多问题(或更可能是它们的子问题)相似的事实,因此现有代码可用于加速进一步的开发。

封闭课程会迫使那些可能在不实际修改您的产品的情况下利用现有代码的人重新发明轮子。(这是我论文的一个关键思想:继承一个类不会修改它!这看起来很普通而且很明显,但它通常被忽略)。

人们经常害怕他们的“公开”课程会被扭曲成无法替代其后代的东西。所以呢?你为什么要关心?没有任何工具可以阻止糟糕的程序员创造出糟糕的软件!

我并不是想将可继承类表示为最终正确的设计方式,我认为这更像是对我对可继承类的倾向的解释。这就是编程的美妙之处——几乎无限的正确解决方案,每个都有自己的优缺点。欢迎您的评论和争论。

最后,我对原始问题的回答:我会完成一个类,让其他人知道我认为该类是分层类树的叶子,我认为它绝对不可能成为父节点。(如果有人认为它实际上可以,那么要么我错了,要么他们没有得到我)。

于 2011-02-13T23:07:56.613 回答