【问题标题】:How to avoid the fragile base class in Java如何避免Java中脆弱的基类
【发布时间】:2013-02-26 01:41:19
【问题描述】:

我正在研究脆弱的基类问题,发现以下论文很有趣:https://www.research.ibm.com/haifa/info/ple/papers/class.pdf

在这篇论文中,有人认为如果 Java 有一个“密封的”访问修饰符会很棒。不像 C# 中的 'sealed',相当于 Java 的 'final' 关键字。所提出的密封机制将无法将这些密封类扩展到它们的包之外。

但是,我发现的有关 FBC 问题的大部分材料都可以追溯到 90 年代末、00 年代初,所以这让我怀疑“问题”是否不再是主要问题。

我知道 Joshua Bloch 提倡限制性地使用继承,尤其是跨库,而且他似乎是 Java 权威。

我知道如何通过创建一组从具有私有构造函数的类继承的最终内部类来实现寡态,但这似乎有点不合适。

提出的密封基本上类似于使类成为默认/包私有,还是今天Java中实际上存在某种类密封机制?

【问题讨论】:

  • 您能否通过一些示例概述您要问的具体内容?这篇论文是大约 10 页的密集散文,按原样详细讨论它可能超出了 SO 的范围。
  • 哦,抱歉,我将对其进行编辑并简要说明。
  • 一个final类不能扩展,一个default/package私有类不能在包外使用,所以有区别。
  • 我想说,IBM 已经从最初的卓越地位下降了很长一段路。在我看来,链接的文档完全是垃圾。你最好忽略它并继续你的学习,就好像你从未读过这篇文章一样。当然——这只是我个人的看法。
  • @OldCurmudgeon:有趣!但是,如果您能解释为什么它是垃圾,那就太好了。我疯狂的业余猜测是,使用这样的关键字不可能使动态类加载有效地工作。

标签: java inheritance polymorphism encapsulation base-class


【解决方案1】:

但是,我发现的有关 FBC 问题的大部分材料都可以追溯到 90 年代末、00 年代初,所以这让我怀疑“问题”是否不再是主要问题。

我认为更重要的是这个问题现在已经被很好地理解了。同样,您不会找到太多最近讨论GOTO 的问题以及如何解决这些问题的论文,不是因为这些问题不再存在,而是因为人们现在知道如何避免它们。

[提议的类密封机制]与将类设为默认/包私有基本上不是一回事吗?

没有。包私有类和“密封”类的相似之处在于两者都不能被包外的类扩展,但它们的不同之处在于前者也不能被包外的类使用。也就是说——如果一个类X是包私有的,那么它包外的一个类甚至不能引用X:没有extends X,没有X x = new X(),没有Class<X> clazz = X.class。但是如果只是密封,那么不同包中的类不能写extends X,但仍然可以写X x = new X()Class<X> clazz = X.class等等。 (同样重要的是,如果Y 是一个子类,它仍然可以写X x = new Y()。所以它仍然可以利用X 的类型层次结构,即使它本身不能扩展X。)

【讨论】:

  • 非常感谢您的精彩回答!
  • 但有一件事:当编程语言变得更加结构化时,goto 语句被省略了;遗产仍然无处不在。我不禁想知道这种仍然不存在的密封机制是否最终会在 Java 中被证明是有用的。
  • @talt:我认为这是因为GOTO 的问题在很久以前就已经很明显了。而GOTO 辩论的一些尾声仍然会影响较新的语言——比如变量范围应该如何与switch 交互,比如异常是否会导致相同的问题等。请注意,一些较新的语言,例如去,do 避免实现继承。如果一些未来的语言支持它,我不会感到惊讶,但有各种限制,比如在 C# 中,switch 语句需要在每个非空 case 的末尾加上 break
  • 感谢 Go 的推荐。我想这表明实现继承不是面向对象的必要性,即使它方便且合乎逻辑。
【解决方案2】:

我知道如何通过创建一组继承自具有私有构造函数的类的最终内部类来实现寡态,但这似乎有点不合适。

我不会说这种技术不合适 - 真正的问题是主流 OOP 语言缺乏按情况定义类型的机制。这样做会将案例与类型混为一谈(除非您通过将子类设为私有来隐藏子类),但这是您在 Java 中的唯一选择。


ruakh's answer 解决了您关于密封机制的问题,所以我将跳过它。至于避免脆弱的基类问题,this paper 提出了一个目前在 Java 中有效的解决方案。这个想法是使所有公共方法final 并根据protected 方法实现它们。这确保子类只能覆盖您认为安全的那些方法。

在主流 OOP 语言中实现的继承问题在于,当它应该是您必须选择加入时,您必须选择退出。也就是说,除了按案例定义类型之外,我不确定继承还有什么其他用途,最好用聚合/组合代替。

【讨论】:

    【解决方案3】:

    实际上并没有像脆弱的基类问题这样的东西,尽管事实上它有一个Wikipedia page,甚至还有一个关于它的StackOverflow question。您找不到任何最近引用它的原因是因为它在 80 年代中期被重命名为 The Incompetent Programmer Problem

    之所以改名是因为有人终于意识到了它描述的问题,在基类中更改一些看似无关紧要的方法对所有继承的子类都会产生深远的影响,实际上并不是oop的问题,而是在您的基类中放置错误代码的问题

    如果您想正确编写 oop 代码并且希望使用继承,那么您肯定必须完全明确地确保您的基类永远包含完全稳定完全可靠 的代码。因为一旦你开始从中派生,你就会陷入困境。如果您发现一旦从基类派生了几次就想更改基类,那么您实际上已经在踢自己的脚了。

    玩弄奇怪的私有层级不是解决办法。

    【讨论】:

    • Re: “如果你想正确地编写 oop 代码并且希望使用继承,那么肯定必须完全明确地确保你的基类只包含完全稳定和完全可靠的代码": nothing 只包含完全稳定和完全可靠的代码——甚至 TeX 也有几个需要修复的错误——所以这相当于说“你不能正确编码 OOP 并利用遗产”。 (这是一个有效的观点,但如果你真的这么想,那么你现在的措辞就是懦弱。)
    • 感觉这将是一个敏感的话题:)
    • @ruakh - 请不要误会我的意思。我知道您可以正确地编写 OOP 代码并且利用继承,因为我一直都这样做。我要说的是,因为所有这些“问题”而害怕继承是完全错误的。您需要做的就是了解,无论您深入到层次结构中的什么,都最好是正确的。顺便说一句 - 我喜欢你以 Tex 为例,但如果他发现你正在使用他的工作来提倡不使用继承,我认为你会非常沮丧 Knuth
    • @OldCurmudgeon:我在哪里提倡不使用继承? (答案:无处可去。我不能,因为我自己一直在使用继承——只是以精心控制的方式。)
    • 我不是一个经验丰富的程序员,但对我来说似乎很难排除需要更改基类代码的可能性。考虑到所涉及的程序员的数量,是否真的有可能避免实现继承容易出错,其中一些人可能不像其他人那样有经验。
    猜你喜欢
    • 1970-01-01
    • 2013-12-06
    • 2017-07-30
    • 1970-01-01
    • 1970-01-01
    • 2011-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多