用更高的可见性重写方法是一种好的实践吗?

8
回答这个问题:如何GUI - 使用paintcomponent()初始化GUI,然后基于鼠标添加GUI,我已经表明了以下观点:
您没有正确覆盖paintComponent()。这是受保护的方法,而不是公共方法。如果您在此方法上添加@ Override 注释,则编译器会发出警告。
但是@peeskillet明智地指出了这一点:
编译器不会因为publicprotectedpaintComponent而抱怨。您可以用更高的可见性来覆盖,但不能使用更低的可见性。publicprotected更高,所以没有问题。
这当然是正确的。但现在出现了这个问题:使用更高的可见性进行覆盖是否是良好的实践?
附录
链接到 JComponent.paintComponent() javadoc。
Netbeans的图像完全没有抱怨:

如果需要的话,可以这样做。否则最好不要进行操作。 - Luiggi Mendoza
1
哇,我被难住了。Java 真的允许你这样做吗?这确实值得在“编程恐怖现场”中发帖。 - Federico Berasategui
8个回答

4
为什么要这样做?一种原因是,如果您在项目的其他地方需要覆盖某个方法,但当前作用域不允许您这样做,则可以使用此方法。通常情况下,使用默认值而不是受保护的值。
通过在项目中的正确包中创建一个新的子类并调整作用域,然后可以在代码中创建一个匿名类,以便在需要时可以重写有问题的方法,而无需使用反射(这会导致代码难以阅读)。
这也是库类永远不应该是final的原因,因为这样就不能做这样的事情。
编辑:我被要求详细说明为什么这与依赖注入相关。我只能根据自己的经验来说,首先是Guice,现在是Dagger。
Dagger使用构造函数注入,这基本上意味着类将获得所有依赖项作为参数提供给构造函数(仅限于那里),而将这些东西绑定在一起的粘合代码列在Dagger@Module中。在此模块中,通常很方便返回一个库类的子类,增加日志语句或提供自定义toString()方法。为了实际执行此操作而不使用反射技巧,类不能是final的,并且您需要能够重写方法并直接使用超类中的字段。因此,没有final类,两种类型至少需要是protected而不是private!
(我强烈推荐使用Dagger,因为它将依赖项解析移动到Java编译器中,使IDE获得了解决问题所需的信息,以便在编译时帮助您解决问题,而不是依赖于运行时中的魔法。我仍然对Dagger设计者在Java生态系统中的洞察力感到惊讶,他们甚至意识到这个想法并实现了它)

+1。这是一个很好的理由。你在评论中提到了依赖注入的一些内容。你能否在你的回答中加入一些关于它的内容? - dic19
因为以下两个原因,这个问题被授予了悬赏奖励:1-提问者喜欢它;2-它提到了“匿名内部类”,这实际上是Java限制的一种解决方法(缺乏委托、事件和真正的泛型)。 - Federico Berasategui
匿名类是最初的Java规范中达成的妥协。完整的lambda支持被认为对他们想要接触的C++程序员来说可能会太过混乱。泛型系统被有意地削弱,以便与现有二进制代码完全向后兼容,这在Sun公司基于他们的Solaris思维方式中至关重要。 - Thorbjørn Ravn Andersen
@ThorbjørnRavnAndersen非常感谢您的编辑。 它应该得到更多的赞,但不幸的是我只能投一票。 我认为赏金很值得。 - dic19

4
从Java教程中的控制类成员的访问
如果其他程序员使用你的类,你希望确保不会发生由于误用而导致的错误。访问级别可以帮助你做到这一点。 - 使用最严格的访问级别使某个成员有意义。除非有充分理由,否则使用private。 - 除了常量外,避免使用public字段。(教程中的许多示例使用public字段。这可能有助于简洁地说明一些要点,但不建议在生产代码中使用。)公共字段倾向于将你与特定实现联系起来,并限制你在更改代码时的灵活性。
这些建议旨在减少耦合: - 为了实现最佳的封装(信息隐藏),你应该始终使用最少可行的可见性声明方法。在小程序中没有问题,但在大程序中,过度紧密耦合的问题非常严重。当一个部分依赖于另一个具体实现时,就会出现耦合。耦合越多,对于进行更改变得越昂贵,因为太多的代码依赖于具体实现。这导致软件腐烂——程序变得越来越不可用,因为它不能轻松升级。
因此,增加可见性实际上并不是一个好主意。这样的代码在未来的开发和维护中可能会引起麻烦。

1
+1,尽管我的真正观点是,“为什么”Java编译器允许这样做,我正在寻找官方来源(来自Java设计团队或类似团队的人员(如果有这样的团队))。 - Federico Berasategui
编译器只检测编译错误。这些是架构方面的问题,超出了语言语法范畴。应该谨慎使用这种能力,但不应禁止,因为有时它很有用。 - Mikhail
1
C#编译器可以检测到这一点。 - Federico Berasategui
@LucasWilson-Richter 这取决于您是否完全控制该代码。另外,“应该”是一个非常强烈的词汇 - 如果我只需要针对现有类进行修订的toString方法,那怎么办? - Thorbjørn Ravn Andersen
@ThorbjørnRavnAndersen 一个强烈观点的强烈说法 :) 而且你绝对是对的,我的咳咳建议取决于你是否控制源代码。 然而,即使您无法控制代码,通常也有比继承更灵活的替代方案。 - Lucas Wilson-Richter
显示剩余3条评论

1
从面向对象编程的角度来看,这没有问题。通过扩展一个类,您可以做以下几件事情:
  • 更改某些功能(通过覆盖方法)
  • 扩展类的接口(通过添加新的公共方法)
当您覆盖方法并更改其可见性时,您正在同时进行两者:显然,您正在更改功能,但也在扩展接口。从类的客户端的角度来看,您实际上正在创建一个新方法在类的接口中。这个新方法碰巧与超类中的一些内部方法具有相同的名称,但客户端不关心(甚至不知道这一点)。那么为什么不呢?
另一方面,还有一个问题:“为什么需要这样做?”超类的作者可能对该方法的可见性有所考虑,并发现其功能并不适用于外部世界。我不是说这样做是错的,但您必须质疑自己增加可见性的动机,因为这可能是您或超类代码中设计不良的提示。

顺便提一下:正如这里所指出的,禁止使用这种语言特性甚至可能会有害。


1
谢谢你的回答,我喜欢这个解释。正如我所说,我们可以添加一个公共方法,并在其中调用超类的受保护方法,使调用公共方法的人无感知。因此,禁止增加可见性似乎没有太多意义。然而,在我看来,这是两件不同的事情:第一种扩展了类接口,但第二种则故意将超类的方法暴露给外部。我认为这不对。 - dic19

1
如果你需要从类/子类之外访问该方法,则解决方案是使用public参数覆盖可见性。最佳实践是将变量和方法设置为尽可能低的可见性。

1
你可以提高方法的可见度以使其更加可见,但不能使其变得不可见,因此 paintComponent 方法可以被覆盖并声明为“public”方法。话虽如此,我想补充一点,你不应该这样做。当覆盖方法时,除非你有非常好的理由使其更加可见,否则应该保持可见性不变。

+1 for 当重写方法时,除非你有非常好的理由使其更可见,否则应该保持可见性不变。 完全同意,但什么是一个“好理由”呢?我想不出任何常见的理由,我们总是可以扩展,创建一个具有另一个不同名称的公共方法,并在此公共方法中调用受保护的方法。这样,受保护的方法就不会直接暴露出来。 - dic19

0
补充一下弗朗索瓦所说的,这里运用的面向对象编程原则是“开闭原则”,即你应该能够扩展一个对象,但不应该修改它。通过覆盖方法来提高其可见性只是像弗朗索瓦所指出的那样进行扩展。

0

有很多理由可以改变可见性。但是请考虑一下,您将更改该类的接口(在API的意义上)。阅读演变的API并将该信息与您的问题相关联。

因此,在我的书中,良好的实践应该是尽可能不改变可见性。否则,您必须仔细考虑后果-即使只是从长远来看。


0
重写方法和其他所有方法一样,只有在你希望外部代码能够调用它们时(除非你别无选择,因为被重写的方法已经声明为public),才应该声明为public。在你的情况下,被重写的方法paintComponent只应该由Swing调用,因此将其保持为protected是最好的选择。

即使您作为设计师对我应该如何命名有自己的看法,但我可能需要使用您的工具来完成您没有预见和允许的任务。例如,我使用基于Java的FTP服务器(因为它允许使用基于内存的文件系统)进行集成测试。为了查看会话是否正确终止,我需要访问会话状态,但这是不被允许且无法在没有反射的情况下完成的。 - Thorbjørn Ravn Andersen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接