【发布时间】:2009-02-06 11:10:44
【问题描述】:
在java中,有没有允许扩展非抽象类的情况?
当有类层次结构时,它似乎总是表明代码错误。你同意吗?为什么/为什么不同意?
【问题讨论】:
标签: java inheritance final extends
在java中,有没有允许扩展非抽象类的情况?
当有类层次结构时,它似乎总是表明代码错误。你同意吗?为什么/为什么不同意?
【问题讨论】:
标签: java inheritance final extends
在某些时候,有非最终的具体类是有意义的。但是,我同意 Kent 的观点——我相信类默认情况下应该是最终的(在 C# 中密封),而 Java 方法默认情况下应该是最终的(就像它们在 C# 中一样)。
正如 Kent 所说,继承需要仔细的设计和文档 - 很容易认为您可以只覆盖单个方法,但不知道在哪些情况下可以从基类调用该方法作为其余部分的一部分实施。
有关此问题的更多讨论,请参阅"How do you design a class for inheritance"。
【讨论】:
我同意 Jon 和 Kent 的观点,但就像 Scott Myers(在 Effective C++ 中)一样,我走得更远。我相信每个班级都应该是abstract 或final。也就是说,只有任何层次结构中的叶类才真正适合直接实例化。所有其他类(即继承中的内部节点)都“未完成”,因此应该是abstract。
进一步扩展普通课程毫无意义。如果类的某个方面值得扩展和/或修改,更简洁的方法是采用该类并将其分离为一个 abstract 基类和一个具体的可互换实现。
【讨论】:
有充分的理由让您的代码保持非最终版本。许多框架,如 hibernate、spring、guice 有时依赖于它们在运行时动态扩展的非最终类。
例如,hibernate 使用代理进行延迟关联获取。 特别是在涉及 AOP 时,您会希望您的类不是最终的,以便拦截器可以附加到它。 另见question at SO
【讨论】:
这个问题同样适用于其他平台,例如 C# .NET。有些人(包括我自己)认为默认类型应该是最终的/密封的,并且需要显式解封以允许继承。
通过继承进行扩展是需要仔细设计的事情,而不是仅仅让一个类型不密封那么简单。因此,我认为允许继承应该是一个明确的决定。
【讨论】:
您最好的参考资料是 Joshua Bloch 的优秀著作《Effective Java》的第 15 条,名为“Design and document for inheritance or else implicit it”。然而,是否允许类扩展的关键不是“它是抽象的”,而是“它在设计时是否考虑到了继承”。有时两者之间存在相关性,但重要的是第二个。举个简单的例子,大多数 AWT 类都被设计为可扩展的,即使是那些非抽象类。
Bloch 章节的总结是,如果祖先不是被设计为从其继承的,那么继承类与其父类的交互可能会令人惊讶和不可预测。因此,类应该有两种 a) 设计用于扩展的类,并且有足够的文档来描述它应该如何完成 b) 标记为 final 的类。 (a) 中的类通常是抽象的,但并非总是如此。对于
【讨论】:
我不同意。如果层次结构不好,则没有理由存在面向对象的语言。如果您查看 Microsoft 和 Sun 的 UI 小部件库,您肯定会发现继承。这就是定义上的所有“坏代码”吗?不,当然不是。
继承可以被滥用,但任何语言特性也可以。诀窍是学习如何正确地做事。
【讨论】:
在某些情况下,您希望确保没有子类化,在其他情况下,您希望确保子类化(抽象)。但是,作为原始作者,您总是不关心也不应该关心的大部分类。这是打开/关闭的一部分。决定应该关闭某事也是有原因的。
【讨论】:
我完全不同意。当具体类知道它们未标记为 final 的方法的可能返回类型时,类层次结构对具体类有意义。例如,一个具体的类可能有一个子类钩子:
protected SomeType doSomething() {
return null;
}
此 doSomething 保证为 null 或 SomeType 实例。假设您有能力处理 SomeType 实例,但没有在当前类中使用 SomeType 实例的用例,但知道此功能在子类中非常有用,而且大多数内容都是具体的。如果当前类可以直接使用,并且默认不对其 null 值执行任何操作,那么将当前类设为抽象类是没有意义的。如果你把它做成一个抽象类,那么你就会在这种类型的层次结构中拥有它的子类:
因此,您有一个不能直接使用的抽象基类,而默认类可能是最常见的情况。在另一个层次结构中,少了一个类,因此可以使用该功能而无需创建一个本质上无用的默认类,因为必须将抽象强加到该类上。
现在,当然,可以使用和滥用层次结构,如果事情没有清楚地记录或类设计得不好,子类可能会遇到问题。但是抽象类也存在同样的问题,你不能仅仅因为你在你的类中添加了“抽象”就摆脱了这个问题。例如,如果上面的“doSomething()”方法的约定要求 SomeType 在通过 getter 和 setter 访问它们时填充 x、y 和 z 字段,那么无论您使用返回 null 的具体类,您的子类都会崩溃作为您的基类或抽象类。
设计类层次结构的一般经验法则非常简单:
我的子类中是否需要我提议的超类的行为? (是/否) 这是您需要问自己的第一个问题。如果您不需要该行为,则无需进行子类化。
我的子类中是否需要我提议的超类的状态? (是/否) 这是第二个问题。如果状态符合您需要的模型,这可能是子类化的候选对象。
如果子类是从提议的超类创建的,它真的是一个 IS-A 关系,还是只是继承行为和状态的捷径? 这是最后一个问题。如果它只是一个捷径并且你不能限定你提议的子类“as-a”超类,那么应该避免继承。可以将状态和逻辑复制并粘贴到具有不同根的新类中,也可以使用委托。
只有当一个类需要行为、状态并且可以认为是超类的子类IS-A(n)实例时,才应该认为它是从超类继承的。否则,存在其他更适合该目的的选项,尽管它可能需要更多的前期工作,但从长远来看它会更干净。
【讨论】:
在某些情况下,我们不希望允许更改行为。例如,String 类,Math。
【讨论】:
Math不应该有任何静态方法。您应该能够实例化它。如果你想改变行为,用你自己的覆盖数学。 (比如说一个快速但不准确的正弦函数。)
我不喜欢继承,因为总是有更好的方法来做同样的事情,但是当您在一个庞大的系统中进行维护更改时,有时以最少更改修复代码的最佳方法是稍微扩展一个类.是的,它通常会导致一个糟糕的代码,但会导致一个有效的代码,并且无需先重写数月。因此,为维护人员提供尽可能多的灵活性是一个不错的方法。
【讨论】: