在java中,是否有允许扩展非抽象类的情况?
当存在类层次结构时,似乎总是表示错误的代码。你同意吗,为什么/为什么不同意?
答案 0 :(得分:12)
有时候有非最终的具体课程是有道理的。但是,我同意Kent的观点 - 我认为默认情况下类应该是final(密封在C#中),并且默认情况下Java方法应该是final的(因为它们在C#中)。
正如肯特所说,继承需要仔细的设计和文档 - 很容易认为你可以只覆盖一个方法,但不知道可以从基类中调用该方法的情况,作为其余部分的一部分。实施
有关此问题的详细讨论,请参阅"How do you design a class for inheritance"。
答案 1 :(得分:11)
我同意Jon和Kent的观点,但就像Scott Myers(在Effective C ++中)一样,我更进一步。我相信每个课程都应该是abstract
或final
。也就是说,只有任何层次结构中的叶类才真正适合直接实例化。所有其他类(即继承中的内部节点)都是“未完成的”,因此应该是abstract
。
通常的课程进一步扩展是没有意义的。如果类的一个方面值得扩展和/或修改,那么更简洁的方法是将一个类分成一个abstract
基类和一个具体的可互换实现。
答案 2 :(得分:5)
此问题同样适用于其他平台,例如C#.NET。有些人(包括我自己)认为类型应该是默认的最终/密封,需要明确地启封以允许继承。
通过继承扩展是需要仔细设计的东西,并不像仅仅保留未密封的类型那么简单。因此,我认为应该是允许继承的明确决定。
答案 3 :(得分:4)
有充分的理由让您的代码保持非最终状态。许多框架,如hibernate,spring,guice,有时依赖于它们在运行时动态扩展的非final类。
例如,hibernate使用代理进行延迟关联提取。 特别是当谈到AOP时,你会希望你的类非最终的,以便拦截器可以附加到它。 另见question at SO
答案 4 :(得分:4)
这里你最好的参考是Joshua Bloch的优秀书籍“Effective Java”的第15项,名为“继承的设计和文件,或者禁止它”。然而,是否应该允许扩展类的关键不是“它是抽象的”,而是“它是否设计了继承”。两者之间有时存在相关性,但这是第二个重要的。举一个简单的例子,大多数AWT类都被设计为扩展,甚至是非抽象的。
布洛赫章节的摘要是,如果祖先不是为了继承而设计的,那么继承类与父母之间的互动可能是令人惊讶和不可预测的。因此,类应该有两种类型:a)旨在扩展的类,并且有足够的文档来描述它应该如何完成b)标记为final的类。 (a)中的类通常是抽象的,但并非总是如此。对于
答案 5 :(得分:3)
在某些情况下,您希望确保没有子类,在其他情况下,您需要确保子类化(抽象)。但是,总是有一大部分的课程,你原作者并不关心,也不应该关心。它是开放/关闭的一部分。决定某些事情应该被关闭也是有原因的。
答案 6 :(得分:2)
我不同意。如果层次结构不好,那么面向对象语言就没有理由存在。如果您查看Microsoft和Sun的UI小部件库,您肯定会找到继承。根据定义,这都是“坏代码”吗?不,当然不是。
继承可以被滥用,但任何语言功能都可以被滥用。诀窍是学习如何正确地做事。
答案 7 :(得分:2)
我不能不同意。当具体类知道它们没有标记为final的方法的可能返回类型时,类层次结构对于具体类是有意义的。例如,具体类可能有一个子类钩子:
protected SomeType doSomething() {
return null;
}
这个doSomething被保证为null或SomeType实例。假设你有能力处理SomeType实例,但是没有用于在当前类中使用SomeType实例的用例,但是要知道这个功能在子类中非常好,而且大多数都是具体的。将当前类作为抽象类是没有意义的,如果它可以直接使用默认情况下对其null值不执行任何操作。如果你把它作为一个抽象类,那么你将把它的子节点放在这种类型的层次结构中:
因此,当默认类可能是最常见的情况时,您有一个不能直接使用的抽象基类。在另一个层次结构中,只有一个较少的类,因此可以使用该功能而不需要创建一个基本无用的默认类,因为抽象只需要强制进入类。
现在,当然,层次结构可以被使用和滥用,如果事情没有清楚地记录或者类没有很好地设计,子类可能会遇到问题。但是同样的问题也存在于抽象类中,你不会因为你在你的类中添加“abstract”而摆脱这个问题。例如,如果上面的“doSomething()”方法的契约要求SomeType在通过getter和setter访问它们时填充x,y和z字段,那么无论你是否使用返回null的具体类,你的子类都会爆炸作为基类或抽象类。
设计类层次结构的一般经验法则是一个简单的问题:
我是否需要在我的子类中建议的超类的行为? (Y / N) 这是你需要问自己的第一个问题。如果您不需要该行为,则没有子类化的参数。
我的子类中是否需要提议的超类的状态? (Y / N) 这是第二个问题。如果状态符合您所需的模型,那么这可能是子类化的标准。
如果子类是从提议的超类创建的,它真的是一个IS-A关系,还是只是一个继承行为和状态的快捷方式? 这是最后一个问题。如果它只是一个快捷方式,并且您无法限定所提议的子类“as-a”超类,则应避免继承。可以使用不同的根将状态和逻辑复制并粘贴到新类中,也可以使用委派。
只有当一个类需要行为,状态并且可以认为超类的子类IS-A(n)实例应该被认为是从超类继承。否则,存在更适合此目的的其他选项,尽管可能需要更多的工作,从长远来看它更清洁。
答案 8 :(得分:1)
在某些情况下,我们不希望允许更改行为。例如,String class,Math。
答案 9 :(得分:1)
我不喜欢继承,因为总是有更好的方法来做同样的事情但是当你在一个庞大的系统中进行维护更改时,有时以最小的方式修改代码的最佳方法是将类扩展一点。是的,它通常导致错误的代码,但是导致工作的代码,而不是几个月的重写。因此,给予维护人员尽可能多的灵活性是一个很好的方法。