12

在 java 中,是否有允许扩展非抽象类的情况?

当有类层次结构时,它似乎总是指示错误的代码。你同意吗,为什么/为什么不同意?

4

10 回答 10

12

当然,有时有非最终的具体类是有意义的。但是,我同意 Kent 的观点——我相信默认情况下类应该是最终的(在 C# 中密封),并且 Java 方法默认情况下应该是最终的(就像在 C# 中一样)。

正如 Kent 所说,继承需要仔细的设计和文档 - 很容易认为您可以只覆盖单个方法,但不知道可以从基类调用该方法作为其余实现的一部分的情况。

有关此问题的更多讨论,请参阅“如何设计继承类” 。

于 2009-02-06T11:22:34.410 回答
11

我同意 Jon 和 Kent 的观点,但就像 Scott Myers(在 Effective C++ 中)一样,我走得更远。我相信每一堂课都应该是abstract,或者final。也就是说,只有任何层次结构中的叶类才真正适合直接实例化。所有其他类(即继承中的内部节点)都是“未完成的”,因此应该是abstract.

进一步扩展通常的课程根本没有意义。如果类的某个方面值得扩展和/或修改,更简洁的方法是采用该类并将其分离为一个abstract基类和一个具体的可互换实现。

于 2009-02-06T11:41:39.753 回答
5

这个问题同样适用于其他平台,例如 C# .NET。有些人(包括我自己)认为默认类型应该是最终的/密封的,并且需要明确解封以允许继承。

通过继承扩展是需要仔细设计的东西,而不是仅仅让一个类型不密封那么简单。因此,我认为允许继承应该是一个明确的决定。

于 2009-02-06T11:17:00.037 回答
5

有充分的理由让您的代码保持非最终状态。许多框架,如 hibernate、spring、guice 有时依赖于它们在运行时动态扩展的非最终类。

例如,hibernate使用代理进行延迟关联获取。特别是在涉及 AOP 时,您会希望您的类不是最终的,以便拦截器可以附加到它。另见SO的问题

于 2009-02-06T11:35:42.403 回答
4

您最好的参考是 Joshua Bloch 的优秀著作“Effective Java”的第 15 条,名为“Design and document for inheritance or else improment it”。然而,是否允许类扩展的关键不是“它是抽象的”,而是“它是否在设计时考虑到了继承”。有时两者之间存在相关性,但重要的是第二个。举个简单的例子,大多数 AWT 类都被设计为可扩展的,即使是那些不是抽象的。

Bloch 章节的总结是,如果祖先不是被设计为从其继承的,那么继承类与其父类的交互可能会令人惊讶和不可预测。因此,类应该分为两种 a) 设计为可扩展的类,并且有足够的文档来描述它应该如何完成 b) 标记为 final 的类。(a) 中的类通常是抽象的,但并非总是如此。为了

于 2009-02-06T15:15:54.597 回答
3

我不同意。如果层次结构不好,就没有理由存在面向对象的语言。如果您查看 Microsoft 和 Sun 的 UI 小部件库,您肯定会发现继承。这就是定义上的所有“坏代码”吗?不,当然不。

继承可以被滥用,但任何语言特性也可以。诀窍是学习如何适当地做事。

于 2009-02-06T11:21:06.113 回答
3

在某些情况下,您希望确保没有子类化,在其他情况下,您希望确保子类化(抽象)。但是,作为原始作者,您总是不关心也不应该关心的大部分类。这是打开/关闭的一部分。决定关闭某事也是有原因的。

于 2009-02-06T11:21:49.013 回答
2

我不能再不同意了。当具体类知道它们未标记为 final 的方法的可能返回类型时,类层次结构对具体类有意义。例如,一个具体的类可能有一个子类钩子:

protected SomeType doSomething() {
return null;
}

此 doSomething 保证为 null 或 SomeType 实例。假设您有能力处理 SomeType 实例,但没有在当前类中使用 SomeType 实例的用例,但知道此功能在子类中非常有用,而且大多数内容都是具体的。如果当前类可以直接使用,默认情况下不对其 null 值执行任何操作,那么将当前类设为抽象类是没有意义的。如果您将其设为抽象类,那么您将在这种类型的层次结构中拥有其子类:

  • 抽象基类
    • 默认类(可能是非抽象的类,只实现受保护的方法,没有别的)
    • 其他子类。

因此,您有一个不能直接使用的抽象基类,而默认类可能是最常见的情况。在另一个层次结构中,少了一个类,因此可以使用该功能而无需创建本质上无用的默认类,因为必须将抽象强加到类上。

  • 默认类
    • 其他子类。

现在,当然,可以使用和滥用层次结构,如果事情没有清楚地记录或类设计得不好,子类可能会遇到问题。但是抽象类也存在同样的问题,你不能仅仅因为你在你的类中添加了“抽象”就摆脱了这个问题。例如,如果上面的“doSomething()”方法的约定要求 SomeType 在通过 getter 和 setter 访问它们时填充 x、y 和 z 字段,那么无论您使用返回 null 的具体类,您的子类都会崩溃作为您的基类或抽象类。

设计类层次结构的一般经验法则几乎是一个简单的问卷:

  1. 我的子类中是否需要我提议的超类的行为?(是/否)这是您需要问自己的第一个问题。如果您不需要该行为,则没有子类化的论据。

  2. 我需要我的子类中我提议的超类的状态吗?(是/否)这是第二个问题。如果状态符合您需要的模型,这可能是子类化的候选者。

  3. 如果子类是从提议的超类创建的,它真的是一个 IS-A 关系,还是只是继承行为和状态的捷径?这是最后一个问题。如果它只是一个捷径并且你不能限定你提议的子类“as-a”超类,那么应该避免继承。可以将状态和逻辑复制并粘贴到具有不同根的新类中,或者可以使用委托。

只有当一个类需要行为、状态并且可以认为是超类的子类IS-A(n)实例时,才应该认为它是从超类继承的。否则,存在其他更适合该目的的选项,尽管它可能需要更多的前期工作,但从长远来看它更清洁。

于 2009-02-06T16:38:51.573 回答
1

在某些情况下,我们不想允许更改行为。例如,字符串类,数学。

于 2009-02-06T11:16:23.720 回答
1

我不喜欢继承,因为总是有更好的方法来做同样的事情,但是当你在一个巨大的系统中进行维护更改时,有时以最少的更改修复代码的最佳方法是扩展一个类。是的,它通常会导致一个糟糕的代码,但会导致一个可以工作的代码,并且不需要先重写几个月。因此,为维护人员提供尽可能多的灵活性是一个不错的方法。

于 2009-02-06T11:21:49.373 回答