为什么在课堂上使用“最终”真的很糟糕?


34

我正在重构PHP OOP旧版网站。

我很想开始在“ make it explicit that the class is currently not extended by anything” 类上使用“最终”。如果我去上课,这可能会节省很多时间,我想知道是否可以重命名/删除/修改protected属性或方法。如果我真的想扩展一个类,我可以删除final关键字来解锁它以进行扩展。

即,如果我参加的班级没有子班,我可以通过将班级标记为最终班来记录该知识。下次访问它时,我不必重新搜索代码库以查看它是否具有子代。从而节省了重构期间的时间。

这似乎是一个明智的节省时间的主意。。。。但是我经常读到,只有在极少数/特殊的场合下才应该使类“最终化”。

也许它搞砸了模拟对象的创建,或者有其他我没有想到的副作用。

我想念什么?


3
我想,如果您完全控制自己的代码,那么它并不是很糟糕,但在OCD方面却有点。如果您错过一堂课(例如,它没有孩子,但不是决赛)怎么办?最好让工具扫描代码并告诉您某个类是否有孩子。将其打包为库并将其提供给其他人后,处理“最终”变得很痛苦。
Job

例如,MbUnit是一个用于.Net单元测试的开放框架,而MsTest并非如此(令人惊讶)。结果,您只是想以MSFT方式运行一些测试就不得不将5k投放到MSFT。其他测试运行程序无法运行由MsTest生成的测试dll-都是由于MSFT使用了最终类。
工作

3
一些有关该主题的博客文章:最终课程:扩展开放,继承关闭(2014年5月;由Mathias Verraes撰写)
hakre

不错的文章。这说明了我对此的想法。我不知道这些注释,所以很方便。干杯。
JW01 2014年

“ PHP 5引入了final关键字,该关键字通过在定义的前面加上final来防止子类覆盖方法。如果该类本身是final定义的,则无法扩展。” php.net/manual/en/language.oop5.final.php一点也不错。
黑色

Answers:


54

我经常读到,仅在极少数/特殊情况下才应将类“定型”。

谁写的错。final放心使用,这没有什么错。它记录了一个类并非在设计时考虑了继承,默认情况下通常适用于所有类:设计一个可以有意义地继承的类不仅要删除final指定符,还需要更多的工作。需要很多照顾。

因此final,默认情况下使用它绝不是坏事。实际上,许多人建议将其设置为默认值,例如Jon Skeet

也许它搞砸了模拟对象的创建……

这确实是一个警告,但如果您需要模拟类,则始终可以求助于接口。当然,这比将所有类都开放给继承(仅出于模拟目的)要好。


1
1+:因为它搞砸了模拟;考虑为每个“最终”类创建接口或抽象类。
Spoike

好吧,这把扳手投入了工作。这种迟到与所有其他答案相矛盾。现在,我不知道该相信什么。(未选中我接受的答案并在重试期间推迟了决定)
JW01 2011年

1
您可以考虑Java API本身,以及查找“ final”关键字的频率。实际上,它并不经常使用。当调用“虚拟”方法(在Java中,如果未将其声明为“ final”或“ private”时,所有方法都是虚拟的)时,由于动态绑定而导致性能可能变差的情况下,可以使用它。 Java API类的子类可能会出现一致性问题,例如子类“ java.lang.String”。Java字符串是每个定义的值对象,以后不得更改其值。子类可能会违反此规则。
强尼·迪

5
@Jonny大多数Java API的设计都不正确。请记住,其中的大多数已经有十多年的历史了,与此同时,已经开发出许多最佳实践。但是请不要相信我:Java工程师自己是这样说的,首先也是最重要的是,乔什·布洛赫(Josh Bloch),他写了详细的内容,例如在《有效的Java》中。请放心,如果今天开发了Java API,它将看起来非常不同,并且final将发挥更大的作用。
康拉德·鲁道夫

1
我仍然不相信更多地使用“ final”已成为最佳实践。在我看来,只有在性能要求决定了“最终”,或者必须确保子类不能破坏一致性时才应使用“最终”。在所有其他情况下,应将必要的文档纳入文档(无论如何都需要编写),而不应作为强制某些使用模式的关键字写入源代码。对我来说,这是在扮演上帝。使用注释谁可能是更好的解决方案。可以添加“ @final”注释,编译器可以发出警告。
强尼·迪

8

如果您想给自己留下一个类没有子类的注释,那么一定要这样做并使用注释,那就是它们的作用。“ final”关键字不是注释,而仅使用language关键字向您发出信号(并且只有您永远知道这意味着什么)是一个坏主意。


17
这是完全错误的!“仅使用语言关键字向您传达某些信息……是个坏主意。” –相反,这正是语言关键字的作用。您的初步告诫“只有您才能知道这意味着什么”,这当然是正确的,但不适用于此。OP正在final按预期使用。没有错。使用语言功能强制约束总是比使用注释更好。
康拉德·鲁道夫

8
@Konrad:除了final应该指出“出于法律原因或其他原因,不应创建此类的子类”,而不是“此类目前没有子类,因此我仍然可以将其受保护的成员弄乱了”。的意图final是“可自由编辑”的对立面,一个final类甚至不应有任何protected成员!
SF。

3
@Konrad Rudolph一点也不微不足道。如果以后其他人要扩展他的课程,则注释“未扩展”与关键字“可能不会扩展”之间存在很大差异。
杰里米(Jeremy)

2
@Konrad Rudolph听起来像是对OOP语言设计提出疑问的好观点。但是我们知道PHP的工作原理,并且采取了另一种允许扩展的方法,除非密封。假装可以合并关键字是可以的,因为“这就是应该的方式”只是防御性。
杰里米(Jeremy)

3
@Jeremy我没有在混用关键字。关键字的final意思是:“(现在)不扩展此类。”仅此而已。无论是PHP的设计采用了这一理念的一点是无关紧要的:它具有final关键字,毕竟,其次,考虑到PHP的设计错综复杂,而且整体上设计得很糟糕,因此从PHP的设计中争论必然会失败。
康拉德·鲁道夫

5

有一篇不错的文章,关于“何时将类声明为final”。引述以下内容:

TL; DR:final如果您的类实现了接口,并且未定义其他任何公共方法,则使它们始终为类

为什么要使用final

  1. 防止厄运的大规模继承链
  2. 令人鼓舞的作品
  3. 迫使开发人员考虑用户公共API
  4. 强制开发人员缩小对象的公共API
  5. final总是可以使一个类具有可扩展性
  6. extends 破坏封装
  7. 您不需要那种灵活性
  8. 您可以自由更改代码

什么时候避免final

最终课程只有在以下假设下才能有效工作:

  1. 最终类实现了一个抽象(接口)
  2. 最后一个类的所有公共API都是该接口的一部分

如果缺少这两个先决条件之一,那么您可能会到达使该类可扩展的时间点,因为您的代码并非真正依赖抽象。

PS感谢@ocramius的出色阅读!


5

类的“最终”表示:您想要一个子类?继续,尽可能多地删除“ final”子类,但是如果它不起作用,请不要向我抱怨。你一个人。

当一个类可以被子类化时,其他人所依赖的行为必须用抽象的术语来描述,子类服从。呼叫者必须写成期望有一定的可变性。文档必须仔细编写;您不能告诉人们“看源代码”,因为源代码还不存在。这就是所有的努力。如果我不希望类被子类化,那是不必要的工作。“最终”明确表示尚未做出努力,并给出了合理的警告。


-1

您可能没有想到的一件事是,对类的任何更改都意味着它必须接受新的QA测试。

除非您确实如此,否则请勿将其标记为最终的。


谢谢。您能否进一步解释。您的意思是,如果我将某个课程标记为final(更改),那么我只需要重新测试它即可?
2011年

4
我会更坚定地说。除非由于类的设计而无法进行扩展,否则不要将其标记为最终的。
S.Lott

谢谢S.Lott-我想了解对final的错误使用对代码/项目有何影响。您是否经历过任何使人感到恐惧的坏故事,使您对少用final。这是第一手经验吗?
2011年

1
@ JW01:final类具有一个主要用例。您有不想扩展的多态类,因为子类可能会破坏多态性。final除非必须防止创建子类,否则不要使用。除此之外,它是没有用的。
S.Lott

@ JW01,在生产环境,你可能不被允许的,除去最后的,因为这不是代码的客户测试和认可。但是,您可能被允许添加额外的代码(用于测试),但是您可能无法做您想做的事情,因为您不能扩展您的课程。

-1

使用“最终”将剥夺其他想要使用您的代码的人的自由。

如果您编写的代码仅适合您自己,并且永远不会向公众或客户发布,那么您当然可以使用自己想要的代码。否则,您将阻止其他人在您的代码上建立代码。我常常不得不使用一种可以轻松扩展以满足自己需求的API,但是后来我被“最终决定”所困扰。

此外,经常有代码,应更好地进行private,但protected。当然,private意味着“封装”并隐藏被视为实现细节的内容。但是作为API程序员,我最好记录一下以下事实:方法xyz被视为实现细节,因此可以在将来的版本中进行更改/删除。因此,尽管有警告,仍将依赖此类代码的每个人都要自己承担风险。但是他实际上可以做到这一点,并且可以重用(希望已经通过测试)代码,并且可以更快地提出解决方案。

当然,如果API实现是开放源代码,则只能删除“最终”或使方法“受保护”,但是您已经更改了代码,需要以补丁形式跟踪更改。

但是,如果该实现是封闭源代码,那么您将无法找到解决方法,或者在最坏的情况下,切换到另一个API时,对自定义/扩展可能性的限制较少。

请注意,我认为“最终”或“私有”并不是邪恶的,但我认为它们只是经常使用,因为程序员在代码重用和扩展方面没有考虑他的代码。


2
“继承不是代码重用”。
quant_dev

2
好吧,如果您坚持一个完美的定义,那是对的。继承表示“是”关系,正是这一事实允许多态性并提供了扩展API的方式。但是实际上,继承是非常经常用来重用代码的。在多重继承的情况下尤其如此。而且,一旦您调用超类的代码,您实际上就在重用代码。
强尼·迪

@quant_dev:OOP中的可重用性首先意味着多态。继承是多态行为(共享接口)的关键点。
猎鹰

@Falcon共享界面!=共享代码。至少不是通常意义上的。
quant_dev

3
@quant_dev:在OOP方面,您得到了可重用性错误。这意味着借助接口共享,单个方法可以对多种数据类型进行操作。这是OOP代码重用的主要部分。继承自动让我们共享接口,从而极大地提高了代码的可重用性。这就是为什么我们谈论OOP中的可重用性的原因。仅使用例程创建库几乎与编程本身一样古老,并且可以用几乎每种语言完成,这很常见。OOP中的代码重用不仅限于此。
猎鹰
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.