【问题标题】:Is it good practice override methods with a higher visibility?是否是具有更高可见性的好习惯覆盖方法?
【发布时间】:2014-01-24 16:54:43
【问题描述】:

回答这个问题:How to GUI - Using paintcomponent() to initialize a GUI and then to add GUI based on mouse 我已经说过了:

您没有正确覆盖paintComponent()。这是一个受保护的 方法,不公开。如果在这个方法上添加@Override注解 然后编译器会抱怨。

但@peeskillet 明智地指出了这一点:

编译器不会抱怨publicprotected paintComponent。您可以覆盖更高的可见性,但不能覆盖 降低一个。 public 高于 protected 所以没有问题。

这当然是真的。但是现在出现了这个问题:是否具有较高的可见性覆盖是一种好的做法?

附录

链接到JComponent.paintComponent()javadoc。

Netbeans 完全没有抱怨的图片:

【问题讨论】:

  • 如果您需要,可以。否则,不要这样做。
  • 哇。我难住了。 java真的让你这样做吗?这真的值得在 Coding Horror 中发表一篇文章……

标签: java overriding class-design


【解决方案1】:

这样做的一个原因是,如果您有一种方法需要在项目的其他地方覆盖,而当前范围不允许您这样做。通常在使用默认而不是受保护时。

通过在项目中其他地方的正确包中创建一个具有修改范围的新子类,然后您可以在代码中您需要它的地方创建一个匿名类,因为您现在可以覆盖麻烦的方法,而不必这样做带有反射(这使得代码非常难以阅读)。

这也是库类永远不会是最终的原因,因为那样你就不能做这样的事情。


编辑:我被要求详细说明为什么这与依赖注入有关。我只能根据自己的经验说话,首先是 Guice,现在是 Dagger。

Dagger 使用构造函数注入,这基本上意味着一个类将获得它的所有依赖项作为构造函数的参数(并且仅存在于此),并且将其绑定在一起的胶水代码列在 Dagger @Module 中。在这个模块中,返回一个库类的子类以增加日志语句或提供自定义 toString() 方法通常非常方便。为了在没有反射技巧的情况下真正这个类不能是最终的,您需要能够覆盖方法并直接使用超类中的字段。因此没有最终类,两种类型都需要至少为protected 而不是private

(我强烈推荐使用 Dagger,因为它移动了 java 编译器中的依赖解析,为 IDE 提供了帮助您在编译时解决问题所需的信息,而不是依赖于运行时中的魔法。我是仍然对 Java 生态系统中的洞察力感到惊讶,Dagger 设计者甚至必须得到这个想法然后实现它)

【讨论】:

  • +1。这是一个很好的理由。您在评论中提到了一些关于依赖注入的内容。您能否在答案中添加一些关于它的内容?
  • 奖励这个问题的原因是:1 - OP 喜欢它,2 - 它提到了“匿名内部类”,这本质上是一种解决 java 限制(缺乏委托和事件以及真正的泛型)的方法。
  • 匿名类是原始 Java 规范中决定的折衷方案。我们发现完全的 lambda 支持对于他们想要接触的 C++ 程序员来说太容易混淆了。泛型系统被故意削弱,以便与现有的二进制代码完全向后兼容,这在 Sun 中基于其 Solaris 思维方式至关重要。
  • @ThorbjørnRavnAndersen 非常感谢您的编辑。它值得更多的支持,但不幸的是我只能投票一次。认为赏金得到了很好的奖励。
【解决方案2】:

来自 Java 教程,Controlling Access to Members of a Class

如果其他程序员使用你的类,你要确保错误 不会发生误用。访问级别可以帮助您做到这一点。

  • 使用对特定成员有意义的最严格的访问级别。除非您有充分的理由不这样做,否则请使用私有。
  • 避免使用除常量之外的公共字段。 (本教程中的许多示例都使用公共字段。这可能有助于说明一些
    简明扼要,但不建议用于生产代码。)公共 字段倾向于将您链接到特定的实现并限制您的 更改代码的灵活性。

此建议针对reduce coupling:

要实现最佳封装(信息隐藏),您应该 总是声明能见度最低的方法。在小 程序确实没有问题,但是在大型程序中这个问题 过度耦合是很严重的。耦合发生在一个部分 取决于另一个的具体实现。越耦合 做出改变的成本就越高,因为太多 代码取决于具体的实现。这会导致软件腐烂 - a 程序变得越来越不可用,因为它不容易 升级了。

因此,提高知名度实际上并不是一个好主意。这样的代码可能会给未来的开发和维护带来麻烦。

【讨论】:

  • +1,虽然我的真正意思是why java 编译器是否允许这样做,我正在寻找一个官方来源(来自 java 设计团队或类似的人(如果有的话)东西)
  • 编译器只检测编译错误。这些是架构问题,超出了语言语法。应谨慎使用此功能,但不应禁止,因为有时它很有用。
  • 不过,C# 编译器会检测到这一点。
  • 我不同意。我发现——尤其是依赖注入——你需要能够轻松地继承给定的类,并轻松地访问这些字段。
  • @ThorbjørnRavnAndersen:您应该提取要替换的类的接口,而不是子类化,将具体类的所有用法更改为使用该接口,然后进行替换以实现那个界面。这意味着您以后可以根据需要完全删除被替换的类,因为新的实现不依赖于它。
【解决方案3】:

如果您需要从其所在的类/子类外部访问该方法,则解决方案是使用公共参数覆盖可见性。最佳做法是让您的变量和方法处于尽可能低的可见性。

【讨论】:

    【解决方案4】:

    您可以提高方法的可见性,使其变得更加可见但不降低可见性,因此可以覆盖paintComponent 并将其声明为public 方法。话虽如此,我要补充一点,你不应该这样做。覆盖该方法时,您应该保持可见性不变,除非您有充分的理由使其更加可见。

    【讨论】:

    • +1 for 当覆盖该方法时,您应该保持可见性不变,除非您有充分的理由让它更加可见。 完全同意,但会是什么充分的理由?我想不出任何常见的原因,我们总是可以扩展,使用另一个不同的名称创建一个公共方法,并在这个公共方法中调用受保护的方法。这样受保护的方法就不会直接暴露。
    【解决方案5】:

    从 OOP 的角度来看,它没有问题。通过扩展一个类,你可以做以下事情:

    • 更改某些功能(通过覆盖方法)
    • 扩展类的接口(通过添加新的公共方法)

    当你重写一个方法并改变它的可见性时,你在做两件事:你 - 显然 - 改变了功能,但也扩展了接口。从类的客户的角度来看,您实际上是在类的接口中创建一个新方法。这个新方法巧合地与超类中的某些内部方法同名,但客户端并不关心(或者甚至不知道这一点)。那你为什么不呢?

    另一方面,有一个问题是“为什么需要这样做?”。超类的作者可能对这个方法的可见性有一些想法,发现它的功能并不适合外界。 我并不是说这样做是错误的,但您必须质疑您提高知名度的动机,因为这可能暗示您或超类的代码中可能存在不良设计。

    顺便说一句:正如here 指出的那样,禁止这种语言功能甚至可能是有害的。

    【讨论】:

    • 谢谢你的回答,我喜欢这个解释。正如我所说,我们可以添加一个公共方法并在其中调用超类的受保护方法,使其对调用公共方法的任何人都是透明的。我认为因此禁止增加知名度没有多大意义。然而,IMO 这是两件不同的事情:第一个扩展了类接口,但第二个故意向外部公开了超类的方法。不要觉得这是对的。
    【解决方案6】:

    与所有其他方法一样,如果您希望外部代码能够调用被覆盖的方法(除非您别无选择,因为被覆盖的方法被声明为公共),则应该将它们声明为public。在您的情况下,重写方法 paintComponent 应该只由 Swing 调用,因此保持它 protected 是最好的选择。

    【讨论】:

    • 即使您作为设计师对我应该如何称呼有意见,我也可能需要使用您的工具来完成您没有想到因此不允许的事情。一个例子 - 我使用基于 java 的 FTP 服务器(因为它允许使用基于内存的文件系统)进行集成测试。为了看到会话被正确终止,我需要访问会话的状态,这是不允许的,而且如果没有反射就不可能做到。
    【解决方案7】:

    补充一下弗朗索瓦所说的,这里的 OOP 原则是“Open Close Principle”,它表示您应该能够扩展,但不能修改对象。正如弗朗索瓦指出的那样,通过覆盖方法来“提高”方法的可见性只是扩展。

    【讨论】:

      【解决方案8】:

      要更改可见性有很多论据。但是想想你会改变那个类的接口(在 API 的意义上)的事实。阅读evolving APIs 并将该信息与您的问题相关联。

      所以在我的书中,最好的做法是不要改变可见性——如果可能的话。否则,您必须仔细考虑后果——即使只是从长远来看。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-03-06
        • 2021-03-24
        • 1970-01-01
        • 2020-08-01
        • 1970-01-01
        • 2016-12-19
        相关资源
        最近更新 更多