在我工作的一个大项目中,我正在考虑建议其他程序员如果没有考虑他们的课程应该如何分类,他们总是密封他们的课程。很少有经验的程序员从不考虑这一点。
我发现奇怪的是,在java和c #classd中是非密封/非最终的pr默认值。我认为密封课程可以大大提高代码的可读性。
请注意,这是内部代码,如果发生我们需要子类的罕见情况,我们可以随时更改。
你有什么经历?我对这个想法遇到了很多阻力。是懒惰的人,他们不能打扰“密封”吗?
答案 0 :(得分:60)
好的,正如许多其他人一样权衡......
是的,我认为建议默认密封课程是完全合理的。
这与Josh Bloch在他的优秀一书Effective Java, 2nd edition中提出的建议一致:
设计继承,或禁止它。
设计继承是 hard ,并且可以使您的实现更少灵活,特别是如果您有虚拟方法,其中一个调用另一个。也许他们是超载,也许他们不是。一个人调用另一个的事实必须记录,否则你不能安全地覆盖任何一种方法 - 你不知道何时会被调用,或者你是否可以安全地调用另一种方法没有冒着堆栈溢出的风险。
现在,如果您以后想要更改哪个方法在更高版本中调用,则不能 - 您可能会破坏子类。因此,在“灵活性”的名义下,您实际上使实现更少更灵活,并且必须更密切地记录您的实现细节。这对我来说听起来不是一个好主意。
接下来是不变性 - 我喜欢不可变类型。我发现它们比可变类型更容易推理。这是Joda Time API比在Java中使用Date
和Calendar
更好的原因之一。但是一个未密封的类永远不会已知是不可变的。如果我接受Foo
类型的参数,我可以依赖Foo
中声明的属性不要随时间更改,但我不能依赖对象本身没有被修改 - 子类中可能有一个可变属性。如果某个虚拟方法的覆盖也使用了该属性,则Heaven帮助我。挥手告别不变性的许多好处。 (具有讽刺意味的是,Joda Time拥有非常大的继承层次结构 - 通常会说“子类应该是不可变的。Chronology
的大型继承层次结构使得移植到C#时很难理解。”
最后,还有过度使用继承的方面。就个人而言,我个人赞成合成而不是继承。我喜欢接口的多态性,偶尔我使用继承实现 - 但它很少适合我的经验。密封课程可以避免他们不恰当地从合成更适合的地方推导出来。
编辑:我还想在Eric Lippert's blog post from 2004向读者指出为什么这么多的框架类都被密封了。有很多地方我希望.NET提供一个接口我们可以为可测试性而努力,但这是一个稍微不同的请求......
答案 1 :(得分:20)
我认为,架构设计决策是为了与其他开发人员(包括未来的维护开发人员)沟通的重要事项。
密封类通知不应覆盖实现。它表示不应该冒充该类。密封是有充分理由的。
如果采取不同寻常的方法来封装所有东西(这是不寻常的),那么您的设计决策现在可以传达非常重要的事情 - 比如该类不是意图继承由原始/创作开发人员。
但是那时你会如何与其他开发者沟通,因为某些事情, 这个类不会被继承?你真的不能。你被困住了。
另外,密封课程并不会提高可读性。我只是没有看到。如果继承是OOP开发中的问题,那么我们就会遇到更大的问题。
答案 2 :(得分:9)
我想我认为我是一位经验丰富的程序员,如果我没有学到任何东西,那就是我在预测未来方面非常糟糕。
打字密封并不难,我只是不想激怒开发人员(可能是我!)谁发现问题可以通过一点继承轻松解决。
我也不知道密封类如何使它更具可读性。您是否试图强迫人们更喜欢组合继承?
答案 3 :(得分:5)
继承课程应该没有任何问题。
只有在有理由不能继承的时候,你才应该密封一个班级。
此外,如果将它们全部密封,只会降低可维护性。每当有人想从你的某个班级继承,他就会看到它是密封的,然后他要么取消印章(弄乱他不应该乱用的代码)或者更糟糕的是:创建一个糟糕的实施你的为自己上课。
然后你将有两个相同的实现,一个可能比另一个更糟糕,还有2个代码需要维护。
最好保持开封。它没有受到伤害。
答案 4 :(得分:4)
坦率地说,我认为在c#中默认没有密封的类是有点奇怪的,并且与其他默认值在该语言中的工作方式不合适。
默认情况下,类为internal
。
默认情况下,字段为private
。
默认情况下,成员为private
。
似乎有一种趋势指向默认情况下最不合理的访问。理所当然地,unsealed
关键字应该以c#而不是sealed
退出。
就个人而言,我宁愿默认密封课程。在大多数时候,当有人写一个类时,他并没有考虑到子类化以及随之而来的所有复杂性来设计它。设计未来的子类应该是一种有意识的行为,因此我宁愿你明确地说明它。
答案 5 :(得分:3)
©Jeffrey Richter
密封有三个原因 上课比开封更好 类:
- 版本控制:当一个类最初被密封时,它可以更改为 没有密封在未来 打破兼容性。但是,曾经 一个课程是开封的,你永远不会 将其改为将来密封 这会破坏所有派生类。 另外,如果是未密封的类 定义任何未密封的虚拟方法, 虚拟方法调用的排序 必须使用新版本进行维护 或者有可能打破 未来派生类型。
- 效果:如上一节所述,调用虚拟 方法的表现不如 调用非虚方法因为 CLR必须查找类型 对象在运行时为了 确定哪种类型定义 打电话的方法。但是,如果是JIT 编译器看到对虚拟的调用 使用密封型的方法,JIT 编译器可以产生更高效的 代码通过调用方法 nonvirtually。它可以这样做,因为 它知道不可能有一个 如果类是密封的派生类。
- 安全性:和可预测性一个类必须保护自己的状态而不是 让自己变得腐败。 当一个类被打开时,派生出来 类可以访问和操作 基类的状态,如果有任何数据字段 或内部操纵的方法 字段是可访问的而不是私有的。 另外,虚拟方法也可以 被派生类重写,并且 派生类可以决定是否 调用基类的实现。 通过制作方法,财产或事件 虚拟,基类正在放弃 一些控制其行为及其行为 州。除非仔细考虑, 这可能导致对象表现 不可预测的,它打开了 潜在的安全漏洞。
答案 6 :(得分:2)
我不喜欢这种思考方式。 Java和c#是OOP语言。这些语言的设计方式是一个班级可以有父母或孩子。就是这样。
有些人说我们应该始终从最受限制的修饰符(私有,受保护的...)开始,并且只有在外部使用它时才将您的成员设置为公共。对我来说,这些人是懒惰的,不想在项目开始时考虑好的设计。
我的回答是:现在以一种好的方式设计你的应用程序。当需要密封时,将您的班级设置为密封,当需要私密时,将其设为私密。不要默认密封。
答案 7 :(得分:2)
如果我正在处理我打算分发的可重用组件,我只会密封类,并且我不希望最终用户继承它,或者如果我知道我不想要其他开发人员那么作为系统架构师在团队中继承它。但是通常有一些原因。
仅仅因为一个类没有被继承,我认为它不应该被自动标记为密封。而且,当我想在.NET中做一些棘手的事情时,它让我感到厌烦,但随后我意识到MS标记了他们的课程密封。
答案 8 :(得分:2)
这是一个非常自以为是的问题,可能会得到一些非常自以为是的答案; - )
那就是说,在我看来,我强烈不愿意让我的课程密封/最终,特别是在开始时。这样做很难推断出预期的可扩展性点,并且几乎不可能在开始时将它们弄清楚。恕我直言,过度使用封装比过度使用多态更糟糕。
答案 9 :(得分:2)
“......考虑他们的班级应该如何分类......”无所谓。
在过去的几年中,至少有六次我发现自己正在诅咒一些开源团队或其他人,因为他们是一个保护和私有的混合组合,因此无法简单地扩展一个类而不复制整个源代码。父母班。 (在大多数情况下,覆盖特定方法需要访问私有成员。)
一个例子是JSTL标签几乎完成了我想要的。我需要覆盖一件小事。不,抱歉,我不得不完全复制父母的来源。
答案 10 :(得分:1)
根据我的经验,我发现密封/最终课程实际上非常罕见;我肯定不会建议默认情况下建议所有课程都是密封/最终的。该规范对代码的状态(即,它是完整的)做出了某种陈述,在开发期间并不总是如此。
我还要补充说,保持一个未密封的类需要更多的设计/测试工作,以确保暴露的行为是明确定义和测试的;重型单元测试至关重要,IMO,以达到对“密封”的支持者所期望的类行为的信心水平。但IMO认为,提高工作量可直接转化为高度自信和更高质量的代码。
答案 11 :(得分:1)
密封类的主要目的是从用户那里拿走继承功能,这样他们就无法从密封类中派生出一个类。你确定要这样做。或者你想开始把所有的课程都密封起来然后当你需要让它继承时你就会改变它......好吧,当一切都在内部和一个团队中时可能会好的但是其他团队将来会使用你的dll每次需要开启一个类时,都不可能重新编译整个源代码.... 我不推荐这个,但这只是我的意见
答案 12 :(得分:1)
你的房子,你的统治。
您也可以使用补充规则:必须注释可以被子类化的类;没有人应该对没有注释的类进行子类化。这条规则并不比你的规则更难理解。