我应该推荐密封类默认?
在一个大项目中,我正在考虑推荐其他程序员,如果他们没有考虑如何将他们的类分类,他们将永远封闭他们的类。 通常经验不足的程序员从不考虑这一点。
我觉得奇怪的是,在Java和C#分类是非密封/非最终公关默认。 我认为使类密封极大地提高了代码的可读性。
请注意,这是在内部代码,我们可以随时改变,如果我们需要inheritance的罕见情况发生。
你有什么经验? 我对这个想法颇有些抵触。 懒惰的人是不是可以打扰“密封”?
好吧,正如许多其他人所称的…
是的,我认为推荐课程默认是封闭的,这是完全合理的。
这与Josh Bloch在其出色的着作Effective Java(第二版)中的推荐一致 :
deviseinheritance,或禁止它。
deviseinheritance是困难的 ,并且可以使你的实现更加灵活,特别是如果你有虚拟方法,其中一个调用另一个。 也许他们是超载,也许他们不是。 一个人调用另一个的事实必须被logging下来,否则你不能安全地重写任何一个方法 – 你不知道什么时候会被调用,或者你是否可以安全地调用另一个方法而不会冒堆栈溢出的风险。
现在,如果您稍后想要更改哪个方法在更高版本中调用哪个方法,则不能 – 您可能会破坏子类。 因此,以“灵活性”的名义,您实际上已经使实现变得更加灵活,并且必须更紧密地logging您的实现细节。 这听起来对我来说不是一个好主意。
接下来是不变性 – 我喜欢不可变的types。 我觉得他们比可变types更容易推理。 这是Joda Time API比使用Java中的Date
和Calendar
更好的原因之一。 但是一个非密封的类永远不可能是不可改变的。 如果我接受一个Foo
types的参数,我可以依靠在Foo
声明的属性不会随着时间的推移而改变,但是我不能依赖于这个对象本身没有被修改 – 可能会有一个可变属性子类。 天堂帮助我,如果这个属性也被一个虚拟方法的覆盖使用。 告别许多不变性的好处。 (具有讽刺意味的是,Joda Time拥有非常大的inheritance层次结构 – 通常事情的意思是“子类应该是不可变的, Chronology
的大型inheritance层次使得在移植到C#时很难理解。
最后是过度使用inheritance的方面。 就我个人而言,在可行的情况下,我倾向于inheritance。 我喜欢接口的多态性, 偶尔我使用inheritance的实现 – 但它很less适合我的经验。 密封课程避免了他们从不适合的地方派生出来。
编辑:我也想在2004年Eric Lippert的博客文章中指出读者为什么这么多的框架类是封闭的。 有很多地方,我希望.NET提供了一个我们可以用来testing的界面 ,但这是一个稍微不同的要求。
我认为,build筑devise决策是与其他开发人员(包括未来的维护开发人员)沟通的重要事情。
密封类传递的实现不应该被覆盖。 它表示不应该模仿class级。 有很好的理由来密封。
如果你采用不寻常的方法来封装所有东西(这是不寻常的),那么你现在的devise决定就会传达一些真正不重要的东西 – 就像这个类不打算由原始/创作开发者inheritance。
但是,那么如何与其他开发人员沟通,不要因为某些事情而inheritance这个类呢? 你真的不能。 你被卡住了
而且,封闭课程并不会提高可读性。 我只是没有看到。 如果inheritance是OOP开发中的一个问题,那么我们有一个更大的问题。
我想我认为我是一个经验丰富的程序员,如果我什么也没有学到,那就是我对未来的预测非常糟糕。
打字密封不难,我只是不想激怒一个开发者(谁可能是我!)谁发现一个问题可以很容易地解决一点点inheritance。
我也不知道如何密封一个类使其更具可读性。 你试图强迫人们更喜欢组成inheritance?
从类inheritance不应该有任何错误。
只有当一个阶级有一个很好的理由,它才应该永远不能被inheritance下来。
另外,如果把它们全部密封起来,只会降低可维护性。 每次有人想要从你的一个class级inheritance,他会看到它被封闭,然后他将要么删除密封(混乱的代码,他不应该混乱)或更糟的:创build一个糟糕的实施你的为自己上课。
然后你会有2个相同的东西的实现,其中一个可能比另一个更糟,还有2个代码要维护。
更好的是保持开封。 没有开封的危害。
坦率地说,我认为在c#中没有被默认封装的类有点奇怪,与其余的默认语言在语言中的工作方式不一样。
默认情况下,类是internal
。 默认情况下字段是private
。 默认情况下会员是private
。
似乎有一个趋势,指出默认情况下最不合理的访问。 这将是坚持一个unsealed
关键字应该退出在C#而不是sealed
。
就个人而言,我宁愿课程被默认密封。 在大多数情况下,当有人写一个类时,他并不是在考虑子类和与之相伴的所有复杂性的情况下进行devise的。 devise未来的子类应该是一个有意识的行为,所以我宁愿你明确地说出来。
©Jeffrey Richter
密封课堂比开封课堂有三个原因:
- 版本控制 :当一个class级原封不动时,可以在未来不打开兼容性的情况下将其改为未封。 然而,一旦一个类被开启,你将永远不能改变它在将来的密封,因为这将打破所有的派生类。 此外,如果未密封的类定义了任何未密封的虚拟方法,则必须使用新版本维护虚拟方法调用的顺序,否则将来有可能中断派生types。
- 性能 :正如前一节所讨论的,调用一个虚拟方法的效果不如调用非虚拟方法,因为CLR必须在运行时查找对象的types,以确定哪种types定义要调用的方法。 但是,如果JIT编译器使用密封types看到对虚方法的调用,则JIT编译器可以通过非虚方法调用该方法来生成更高效的代码。 它可以做到这一点,因为它知道如果封闭的类是不可能的派生类。
- 安全性和可预测性一个class级必须保护自己的状态,不要让自己变得腐败。 当一个类被解封时,如果有任何内部操作字段的数据字段或方法是可访问的而非私有的,则派生类可以访问和操作基类的状态。 另外,一个虚拟方法可以被派生类覆盖,派生类可以决定是否调用基类的实现。 通过虚拟方法,属性或事件,基类放弃了对其行为和状态的一些控制。 除非经过仔细考虑,否则可能会导致对象的行为不可预测,并且会带来潜在的安全漏洞。
如果我正在处理一个我打算分发的可重用组件,并且我不希望最终用户inheritance它,或者如果我知道我不想让团队中的其他开发人员成为系统架构师从它inheritance。 但是通常有一些原因。
仅仅因为一个阶级不是被遗传的,我不认为它应该被自动标记为密封。 另外,当我想要在.NET中做一些棘手的事情时,它会让我感到懊恼,但是后来认识到MS标记了大量的密封类。
这是一个非常有见地的问题,可能会得到一些非常斟酌的答案;-)
这就是说,在我看来,我宁愿不让我的课程成为密封/最终的课程,特别是在开始的时候。 这样做很难推断出扩展点,而且在开始时几乎不可能。 恕我直言,过度使用封装比过度使用多态。
“…考虑他们的class级应该如何分类……”应该没有关系。
在过去的几年中,至less有六七次,我发现自己诅咒一些开放源代码团队或者其他一些混合了受保护和私有的团队,这样就无法简单地扩展一个类而不复制整个父类的源代码。 (在大多数情况下,覆盖一个特定的方法需要访问私人成员。)
一个例子是几乎做我想要的JSTL标签。 我需要重写一件小事。 不,对不起,我不得不完全复制父母的来源。
我不喜欢这种想法。 Java和c#被做成OOP语言。 这些语言的devise方式是一个class级可以有一个父母或一个孩子。 而已。
有些人说,我们应该始终从最受限制的修饰语(私人,保护…)开始,并且只有当您在外部使用时才将您的成员设置为公开。 对我而言,这些人懒惰,不想在项目开始时考虑好的devise。
我的答案是:现在就devise好你的应用程序。 设置你的class级在需要密封的时候密封,当需要私密的时候密封。 不要默认密封。
密封类的主要目的是从用户中拿走inheritance特性,所以他们不能从密封类中派生出一个类。你确定要这样做。 或者你想开始让所有的课程都是密封的,然后当你需要使它可以inheritance时,你会改变它..那么当每个东西都在内部和一个团队,但在未来的其他团队使用你的dll每次需要开启一个类时,都不可能重新编译整个源代码….我不会推荐这个,但那只是我的意见
你的房子,你的规则。
你也可以有相应的规则:一个可以被分类的类必须被注释; 没有人应该inheritance未注释的类。 这个规则并不比你的规则更难遵循。
根据我的经验,我发现密封课/最终课是非常罕见的。 我肯定不会build议所有的课程默认密封/最终。 该规范对代码的状态做了一定的陈述(即完整),在开发时并不一定总是如此。
我还会补充说,让一个类开封需要更多的devise/testing工作,以确保暴露的行为是明确的和testing的; 重要的unit testing是至关重要的,国际海事组织(IMO)对于“密封”的支持者所期望的class级行为达到一定的信心。 但国际海事组织认为,提高的努力水平直接转化为高度自信和高质量的代码。