为什么要避免Java继承“扩展”


40

詹姆斯·戈斯林说

“您应该尽可能避免实现继承。”

而是使用接口继承。

但为什么?如何避免使用关键字“ extends”继承对象的结构,同时使我们的代码面向对象?

有人可以在“在书店订购书”的情况下,提供一个面向对象的示例来说明此概念吗?



问题是您只能扩展一个班级。

Answers:


38

戈斯林没有说要避免使用extends关键字……他只是说过不要将继承作为实现代码重用的一种手段。

继承本身并不坏,它是创建OO结构时使用的非常强大的(必要的)工具。但是,如果使用不当(即用于创建对象结构以外的其他用途),则会创建紧密耦合且难以维护的代码。

因为应该使用继承来创建多态结构,所以可以通过接口轻松地完成继承,因此在设计对象结构时要声明使用接口而不是类。如果您发现自己使用扩展作为避免将代码从一个对象复制粘贴到另一个对象的一种方式,则可能是您在使用它时错了,最好与专门的类一起使用,以处理此功能并通过合成来共享该代码。

阅读并了解Liskov替代原理。每当您要扩展类(或与此相关的接口)时,请问自己是否违反了该原理,如果是,则您很有可能(滥用)以不自然的方式使用继承。它很容易做到,而且似乎通常是对的,但这会使您的代码难以维护,测试和发展。

---编辑 此答案是一个很好的论据,可以更笼统地回答该问题。


2
(实现)继承对于创建OO结构不是必不可少的。您可以不使用OO语言。
Andres F.

你能举个例子吗?我从未见过没有继承的OO。
Newtopian '16

1
问题是没有关于OOP的公认定义。(顺便说一句,连艾伦·凯(Alan Kay)都后悔对他的定义的一般解释,因为它当前的重点是“对象”而不是“消息”)。这是您对问题的回答的一种尝试。我同意在没有继承的情况下看到OOP是不常见的。因此,我的论点仅仅是,它不是必需的;)
Andres F.

9

在面向对象编程的早期,实现继承是代码重用的金锤。如果某个类中需要某个功能,那么要做的就是从该类中公开继承(这些日子C ++比Java更为流行),并且您可以“免费”获得该功能。

除此以外,它并不是真正免费的。结果是几乎难以理解的极其复杂的继承层次结构,包括难以处理的菱形继承树。这是Java设计人员选择不支持类的多重继承的部分原因。

高斯林的建议(和其他陈述)像是对相当严重的疾病的强效药物,很可能有些婴儿被洗澡水扔掉了。使用继承为真正的类之间的关系建模是没有错的is-a。但是,如果您只是想使用另一个类中的功能,那么最好先研究其他选项。


6

继承最近聚集了相当可怕的声誉。避免这种情况的一个原因是“组成优先于继承”的设计原则,该原则从根本上鼓励人们不要在不是真正关系的地方使用继承,只是为了节省一些代码编写。这种继承会导致一些难以发现和修复的错误。您的朋友可能建议改用接口的原因是因为接口为分布式api提供了更灵活和可维护的解决方案。它们通常允许在不破坏用户代码的情况下对api进行更改,而暴露类通常会破坏用户代码。.Net中最好的例子是List和IList之间的用法区别。


5

因为您只能扩展一个类,但可以实现许多接口。

我曾经听说继承定义了您的身份,但是接口定义了您可以扮演的角色。

接口还可以更好地利用多态性。

那可能是原因的一部分。


为什么要下票?
Mahmoud Hossam

6
答案就在马车前面。Java只允许您扩展一个类,这是因为Gosling(Java的设计者)明确表达了。这就像说孩子在骑自行车时应该戴头盔,因为那是法律。没错,但是法律和建议都源于避免头部受伤。
JohnMcG 2011年

@JohnMcG我不是在解释Gosling所做的设计选择,我只是向编码人员提供建议,编码人员通常不会理会语言设计。
Mahmoud Hossam

1

我也被教过这一点,并且在可能的地方我更喜欢接口(当然,在有意义的地方我仍然使用继承)。

我认为它做的一件事是将您的代码与特定的实现分离。假设我有一个名为ConsoleWriter的类,该类被传递到方法中以将某些内容写出并将其写入控制台。现在让我说我想切换到打印到GUI窗口。好了,现在我必须修改方法或编写一个以GUIWriter作为参数的新方法。如果我先定义一个IWriter接口,然后将该方法放入IWriter中,那么我可以从ConsoleWriter(它将实现IWriter接口)开始,然后再编写一个新的类GUIWriter(它也实现IWriter接口),然后我只需要切换正在传递的课程即可。

另一件事(对于C#是正确的,对于Java不确定)是您只能扩展1个类,但可以实现许多接口。可以说我有一个名为Teacher,MathTeacher和HistoryTeacher的类。现在,MathTeacher和HistoryTeacher从Teacher继承而来,但是如果我们要一个代表既是MathTeacher又是HistoryTeacher的人的类,该怎么办?当您一次只能从一个类继承多个类时,它可能会变得非常混乱(有办法,但它们并不是最佳选择)。使用接口,您可以有2个称为IMathTeacher和IHistoryTeacher的接口,然后有一个从Teacher扩展并实现这2个接口的类。

使用接口的一个缺点是,有时我会看到人们重复代码(因为您必须为每个类创建实现,因此必须实现接口),但是对于此问题有一种干净的解决方案,例如使用诸如委托之类的东西(不确定等价于Java)。

人们认为在继承上使用接口的最大原因是实现代码的解耦,但不要认为继承是有害的,因为它仍然非常有用。


2
“您只能扩展1类,但可以实现许多接口”对于Java和C#都是如此。
FrustratedWithFormsDesigner

代码重复问题的一种可能解决方案是创建一个实现该接口的抽象类并对其进行扩展。但是要小心使用;我只看到少数情况是有道理的。
Michael K

0

如果从一个类继承,则不仅要依赖该类,而且还必须遵守该类的客户创建的任何隐式合同。鲍勃叔叔提供了一个很好的例子,说明如何使用基本的Square扩展Rectangle难题轻松地违反Liskov换位原理。接口由于没有实现,因此也没有隐藏的合同。


2
挑剔的一点:即使对象是可变的,即使仅使用纯接口,仍然会出现圆形椭圆问题
rwong
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.