为什么要使用接口,多重继承与接口,接口的优点?


71

我对此事仍然有些困惑。我到目前为止发现的是

(这里已经问过类似的问题,但我还有其他几点。)

  1. 接口是仅抽象方法和最终字段的集合。

  2. Java中没有多重继承。

  3. 接口可用于在Java中实现多重继承。

  4. 继承的一个强项是我们可以在派生类中使用基类的代码,而无需再次编写。可能这是继承在那里最重要的事情。

现在..

Q1。由于接口仅具有抽象方法(无代码),因此,我们如何说如果实现任何接口,那么它就是继承?我们没有使用它的代码。

Q2。如果实现接口不是继承,那么如何使用接口实现多重继承?

Q3。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在实现它的所有类中一次又一次地编写代码。

那为什么要建立接口呢?

注意:我发现一种情况下接口是有用的。它的一个示例就像在Runnable接口中,我们有public void run()方法,在其中定义线程的功能,并且内置了编码,该方法将作为单独的线程运行。因此,我们只需要编写线程中的操作代码,Rest是预定义的。但是,这也可以使用抽象类和全部来实现。

那么使用接口的确切好处是什么?使用接口实现的真的是多重继承吗?

Answers:


41

Q1。由于接口仅具有抽象方法(无代码),因此,我们如何说如果实现任何接口,那么它就是继承?我们没有使用它的代码。

我们不能。接口不用于实现多重继承。他们用更安全的替代它,尽管功能稍差一些。注意关键字implements而不是extends

Q2。如果实现接口不是继承,那么如何使用接口实现多重继承?

他们不是。使用接口,单个类可以具有多个“视图”,不同的API或功能。例如,A类可以RunnableCallable在同一时间,而这两种方法都有效地做同样的事情。

Q3。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在实现它的所有类中一次又一次地编写代码。

接口是一种多重继承,它没有引入任何问题(例如Diamond问题)。

接口的用例很少:

  1. 对象实际上有两个身份:一个Tank 既是VehicleWeapon。您可以使用Tank预期前者或后者(多态性)的实例。这在现实生活中很少出现,实际上是多重继承会更好(或特质)的有效示例。

  2. 简单的责任:Tank游戏中对象的实例还Runnable可以让您在线程中执行它并ActionListener响应鼠标事件。

  3. 回调接口:如果对象实现给定的回调接口,则会收到有关其生命周期或其他事件的通知。

  4. 标记接口:不添加任何方法,但可通过instanceof发现对象功能或愿望轻松访问。SerializableCloneable就是这样的例子。

您正在寻找的是特征(例如在Scala中),不幸的是在Java中不可用。


您能否详细说明使用接口的好处?
gprathour 2011年

@GPSingh:我尝试在下面添加几点Q3。您还有其他问题吗?
Tomasz Nurkiewicz 2011年

问题二的答案-“它们不是”用于实现多重继承,这与问题三的答案-“接口属于多重继承”是矛盾的。这使它有些混乱。
大卫·吉伦

Q1:您说过它不是用来实现多重继承的,而在Q2:您说过是一种多重继承。您能说得更清楚些吗?
Ashok kumar Ganesan,

25

接口是最终静态字段和抽象方法的集合(新Java 8增加了对在接口中具有静态方法的支持)。

当我们知道必须完成某些任务但应该如何完成时,就可以创建接口。换句话说,我们可以说我们实现了接口,以便我们的类以特定的方式开始表现。

让我举例说明,我们都知道动物是什么。就像狮子是动物,猴子是动物,大象是动物,牛是动物等等。现在我们知道所有动物都确实会吃东西并入睡。但是每只动物可以吃东西或睡觉的方式可能有所不同。就像狮子通过猎杀其他动物而进食一样,那里的牛吃草。但是都吃。这样我们可以有一些伪代码,

interface Animal {
    public void eat();
    public void sleep();   
}

class Lion implements Animal {
    public void eat() {
        // Lion's way to eat
    }

    public void sleep(){
         // Lion's way to sleep
    }
}

class Monkey implements Animal {
    public void eat() {
        // Monkey's way to eat
    }

    public void sleep() {
        // Monkey's way to sleep
    }
}

根据上述伪代码,任何能够进食或睡眠的东西都将被称为动物,或者可以说所有动物都必须进食和睡眠,但进食和睡眠的方式取决于动物。

在接口的情况下,我们仅继承行为,而不在类继承的情况下继承实际代码。

Q1。由于接口仅具有抽象方法(无代码),因此,我们如何说如果实现任何接口,那么它就是继承?我们没有使用它的代码。

实现接口是另一种继承。它与类的继承不同,因为继承子类从基类中获取要重用的真实代码。

Q2。如果实现接口不是继承,那么如何使用接口实现多重继承?

据说因为一个类可以实现多个接口。但是我们需要了解,这种继承不同于类的继承。

Q3。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在实现它的所有类中一次又一次地编写代码。

实现接口会使类不得不强制其必须重写其所有抽象方法。

这里这里在我的书中阅读更多内容


8
我觉得您在代码中构建的结构与扩展接口相比,与扩展接口相比,会更好,更合理。我认为这是一个相当糟糕的例子
冲动福克斯

1
@gprathour我同意,所以请提供一个更好的例子:)
John R Perry

1
如果您想获得一个更实际的示例,说明何时使用界面以及何时不使用界面。考虑编写一个必须保存一些数据的应用程序。在开始时,您知道要保存数据并再次加载它,但是在方法上可能会有所冲突:因此,您创建了IOInterface。您首先选择将数据写入一个普通文件,然后创建继承IOInterface的FileContext。然后,您发现将其保存在SQLite数据库中实际上是一件事情。然后,您可以编写一个继承IOInterface的SQLiteContext。代码已添加,未删除,仍兼容。加法编码。
rasmus91

10

Q1。由于接口仅具有抽象方法(无代码),因此,我们如何说如果实现任何接口,那么它就是继承?我们没有使用它的代码。

不幸的是,在通俗的用法中,inheritance当一个类实现接口时,该词仍然经常使用,尽管interface implementation这是一个更好的术语-IMO,但该术语inheritance应严格用于具体或抽象类的继承。在诸如C ++和C#之类的语言中,相同的语法(即Subclass : SuperclassClass : Interface)用于类继承和接口实现,这可能会导致滥用单词inheritance与接口的现象更加普遍。Java使用不同的语法来extend编写类,而不是implement使用接口,这是一件好事。

Q2如果实现接口不是继承,那么如何使用接口实现多重继承?

您可以通过组合实现多重继承的“效果”-通过在一个类上实现多个接口,然后为该类上所有接口所需的所有方法,属性和事件提供实现。对具体类执行此操作的一种常见技术是,通过将实现“连接”到每个内部类实现来与实现外部接口的类进行“ has-a”(组成)关系。(诸如C ++之类的语言确实直接支持多个具体继承,但是会产生其他潜在问题,例如菱形问题)。

Q3使用接口有什么好处?他们没有任何代码。我们需要在实现它的所有类中一次又一次地编写代码。

接口允许现有的类(例如框架)与您的新类进行交互,而无需通过“已知”的方式进行交互,因为它们能够通过已知的接口进行通信。将接口视为合同。通过在类上实现此接口,您将受合同约束以履行其要求的义务,并且在实现此合同后,您的类应能够与使用该接口的任何其他代码互换使用。

现实世界的例子

一个“现实世界”的例子是特定国家/地区的壁装电源插座的法规和惯例(接口)。插入插座的每个电器都需要满足当局为插座定义的规格(合同),例如,电线,零线和地线的位置,开/关开关的位置和颜色以及一致性通过电源提供的电压,频率和最大电流interface接通电源时。

将接口(即标准的壁式插座)去耦而不是仅将电线焊接在一起的好处是,您可以插入(并拔出)风扇,水壶,双适配器或明年发明的新设备。 ,即使在设计界面时不存在此设备。为什么?因为它将符合接口的要求。

为什么要使用接口?

接口非常适合于类的松散耦合,并且是Bob叔叔的SOLID范式的主要支柱之一,尤其是Dependency Inversion PrincipleandInterface Segregation Principles

简而言之,通过确保类之间的依赖关系仅耦合在接口(抽象)上,而不耦合在其他具体类上,它允许将依赖关系替换为满足接口要求的任何其他类实现。

在测试中,依赖项的存根和模拟可以用于对每个类进行单元测试,并且可以“监视”该类与依赖项之间的交互。


1
您首先要弄清楚interface implementationvs concrete inheritance,这是非常重要的区别。但是,您interface稍后使用该术语可能会使现实世界中的示例感到困惑。可以做些事情来提高清晰度吗?例如真实世界的例子是什么?接口,继承?下一个短语可能会使人相信这是接口的示例,但是该示例似乎是一个具体的实现。
史蒂夫·布佐纳斯

1
谢谢史蒂夫-您说得对,我的回答需要一些TLC。
StuartLC '18 -4-3

9

我已经搜索了几天,甚至几周,试图了解界面并似乎阅读了相同的通用帮助。我不是想贬低捐款额,但我认为灯泡只是点击了一下,所以我很生气:))

我更喜欢保持简单愚蠢,因此将提供我新发现的接口视图。

我是一个随意的编码器,但我想发布用VB.NET编写的代码(其他语言的原理相同),以帮助其他人理解界面。

如果我有错,请在后续评论中告知其他人。

说明

表单上的三个按钮,单击每个按钮将对接口变量(_data)保存不同的类引用。不同类引用接口变量的全部要点是我不了解,因为它似乎是多余的,然后它的功能在msgbox中变得很明显,我只需要调用SAME方法来执行我需要的任务,在这种情况下案例“ GetData()”,它使用接口引用变量(_data)当前持有的类中的方法。

因此,无论如何,我希望从数据库,Web或文本文件中获取数据,只有使用相同的方法名称才能完成此操作;该实现背后的代码...我不在乎。

这样就可以轻松使用接口更改每个类代码,而无需任何依赖...这是OO和封装的主要目标。

何时使用

代码类,如果您注意到用于方法的相同动词,例如“ GetData()”,那么在该类上实现接口并将该方法名称用作抽象/接口是一个很好的选择。

我衷心希望这对这个困难的人有帮助。

Public Class Form1

Private _data As IData = Nothing

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
    _data = New DataText()
    MsgBox(_data.GetData())
End Sub

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
    _data = New DataDB()
    MsgBox(_data.GetData())
End Sub

Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
    _data = New DataWeb()
    MsgBox(_data.GetData())
End Sub

End Class

Public Interface IData
Function GetData() As String
End Interface

Friend Class DataText : Implements IData

Friend Function GetData() As String Implements IData.GetData
    Return "DataText"
End Function

End Class

Friend Class DataDB : Implements IData

Friend Function GetData() As String Implements IData.GetData
    Return "DataDB"
End Function

End Class

Friend Class DataWeb : Implements IData

Friend Function GetData() As String Implements IData.GetData
    Return "DataWeb"
End Function

End Class

我相信您的意思可能instance variable不是interface variable
Steve Buzonas

5

这是一个非常老的问题,并且Java-8版本为接口添加了更多功能和功能。

接口声明可以包含

  1. 方法签名
  2. 默认方法
  3. 静态方法
  4. 常量定义。

接口中具有实现的唯一方法是默认方法和静态方法。

接口用途:

  1. 定义合同
  2. 链接不相关的类具有一个功能(例如,实现Serializable接口的类之间可能有也可能没有任何关系,除非实现该接口
  3. 提供可互换的实现,例如Strategy_pattern
  4. 默认方法使您可以向库的接口添加新功能,并确保与为这些接口的较早版本编写的代码二进制兼容
  5. 使用静态方法在库中组织帮助程序方法(可以将特定于接口的静态方法保留在同一接口中,而不是在单独的类中)

看看这个与SE相关的代码示例问题,以更好地理解概念:

我应该如何解释接口和抽象类之间的区别?

回到您的查询:

Q1。由于接口仅具有抽象方法(无代码),因此,我们如何说如果实现任何接口,那么它就是继承?我们没有使用它的代码。

Q2。如果实现接口不是继承,那么如何使用接口实现多重继承?

接口可以包含用于静态默认方法的代码。这些默认方法提供向后兼容性,而静态方法提供帮助程序/实用程序功能。

您不能在Java中拥有真正的多重继承,而接口不是获取继承的方法。接口只能包含常量。因此,您不能继承状态,但是可以实现行为。

您可以用功能替换继承接口为实现类提供了多种功能。

Q3。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在实现它的所有类中一次又一次地编写代码。

请参阅我的答案中的“接口的使用”部分。


5

老问题。我很惊讶没有人引用以下标准资料:Java: James Gosling的概述,《设计模式:由四人组成的可重用的面向对象软件元素》或Joshua Bloch的《有效Java》(在其他资料中)。

我将从报价开始:

接口只是对象响应的一组方法的规范。它不包括任何实例变量或实现。接口可以被多重继承(与类不同),并且可以比通常的刚性类继承结构更灵活地使用它们。(高斯林,第8页)

现在,让我们一个个地回答您的假设和问题(我将自动忽略Java 8功能)。

假设条件

接口是仅抽象方法和最终字段的集合。

abstract在Java接口中看到关键字了吗?否。那么您不应该将接口视为抽象方法的集合。也许您被C ++所谓的接口误导了,它们是仅具有纯虚方法的类。根据设计,C ++不具有(也不需要具有)接口,因为它具有多重继承。

正如Gosling解释的那样,您应该将接口视为“对象响应的一组方法”。我喜欢看界面和相关文档视为服务合同。它描述了从实现该接口的对象可以期望得到的结果。文档应指定前置条件和后置条件(例如,参数不应该为null,输出始终为正,...)和不变式(不修改对象内部状态的方法)。我认为,这份合同是OOP的心。

Java中没有多重继承。

确实。

JAVA忽略了许多C ++很少使用,理解不清,令人困惑的功能,以我们的经验,这些功能带来的痛苦多于好处。这主要包括运算符重载(尽管它确实有方法重载),多重继承和广泛的自动强制。(高斯林,第2页)

没有补充。

接口可用于在Java中实现多重继承。

不,简单,因为Java中没有多重继承。往上看。

继承的一个强项是我们可以在派生类中使用基类的代码,而无需再次编写。可能这是继承在那里最重要的事情。

这就是所谓的“实现继承”。如您所写,这是重用代码的便捷方法。

但是它有一个重要的对应物:

父类通常定义至少部分子类的物理表示形式。因为继承将子类公开给其父级实现的详细信息,所以人们常说“继承破坏了封装” [Sny86]。子类的实现与其父类的实现紧密相关,以至于父类实现的任何更改都将迫使子类进行更改。(GOF,1.6)

(在Bloch,第16项中有类似的引用。)

实际上,继承还有另一个目的:

类继承结合了接口继承和实现继承。接口继承根据一个或多个现有接口定义了一个新接口。实现继承根据一个或多个现有实现定义了一个新实现。(GOF,附录A)

两者都使用extendsJava中的关键字。您可能具有类的层次结构和接口的层次结构。第一个承担执行,第二个承担义务。

问题

Q1。由于接口仅具有抽象方法(无代码),因此如何说如果实现任何接口,那么它就是继承?我们未使用其代码。**

接口的实现不是继承。它的实现。因此就是关键词implements

Q2。如果实现接口不是继承,那么如何使用接口实现多重继承?**

Java中没有多重继承。往上看。

Q3。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在实现它的所有类中一次又一次地编写代码。/然后为什么要创建接口?/使用接口的确切好处是什么?使用接口实现的真的是多重继承吗?

最重要的问题是:您为什么要进行多重继承?我可以想到两个答案:1.给对象赋予多重类型;2.重用代码。

给对象赋予多重类型

在OOP中,一个对象可能具有不同的类型。例如,在Java中,一个ArrayList<E>有以下几种类型:SerializableCloneableIterable<E>Collection<E>List<E>RandomAccessAbstractList<E>AbstractCollection<E>Object(我希望我没有忘记任何人)。如果对象具有不同的类型,则各种消费者将可以使用它而无需了解其特殊性。我需要一个,Iterable<E>而你给我一个ArrayList<E>?没关系。但是,如果我现在需要a,List<E>而您给我a ArrayList<E>,也可以。等等。

如何在OOP中键入对象?您以Runnable界面为例,此示例非常适合说明该问题的答案。我引用了官方Java文档:

另外,Runnable提供了一种在不继承Thread的情况下使类活动的方法。

重点是:继承是键入对象的便捷方法。您想创建一个线程吗?让我们对该Thread类进行子类化。您希望对象具有不同的类型,让我们使用多重继承。啊 它在Java中不存在。(在C ++中,如果您希望对象具有不同的类型,则可以采用多重继承。)

那么如何给对象赋予多重类型呢?在Java中,您可以直接输入对象。这就是您implementsRunnable接口进行类化时要做的事情。Runnable如果您是继承者,为什么要使用?也许是因为您的班级已经是另一个班级的子类A。现在您的课程有两种类型:ARunnable

通过多个接口,您可以为对象提供多种类型。您只需要创建一个具有implements多个接口的类。只要您遵守合同,就可以。

重用代码

这是一个困难的课题;我已经引用了GOF来打破封装。其他答案提到钻石问题。您还可以想到“单一责任原则”:

一个班级只有一个改变的理由。(Robert C. Martin,敏捷软件开发,原理,模式和实践)

拥有家长班可能会给班级一个改变的理由,除了它自己的责任:

超类的实现可能因发行版本而异,如果确实如此,则即使未触摸其代码,子类也可能会中断。结果,子类必须与其父类一起演化(Bloch,项目16)。

我会添加一个比较平淡的问题:当我尝试在类中查找方法的源代码并且找不到时,总是有一种奇怪的感觉。然后我记得:必须在父类的某个位置定义它。或在祖父母阶级中。甚至更高。在这种情况下,一个好的IDE是有价值的资产,但是在我看来,它仍然是一种神奇的东西。接口层次结构没有什么相似之处,因为Javadoc是我唯一需要的东西:IDE中有一个键盘快捷键,我知道了。

继承howewer具有以下优点:

在包中使用继承是安全的,包中的子类和超类实现在同一程序员的控制下。当扩展专门为扩展而设计和记录的类时,使用继承也是安全的(项目17:为继承而设计或记录,否则禁止它)。(批注,项目16)

Java中的一个类“为扩展专门设计和记录”的示例是AbstractList

但是布洛赫(Bloch)和政府(GOF)坚持这一观点:“从喜好看,而不是继承”:

委派是一种使组合与继承一样强大而可重用的方法[Lie86,JZ91]。在委托中,处理请求涉及两个对象:接收对象将操作委托给委托。这类似于将请求推迟到父类的子类。(GOF第32页)

如果使用组合,则无需一次又一次地编写相同的代码。您只需创建一个处理重复的类,然后将该类的实例传递给实现该接口的类。这是重用代码的非常简单的方法。这可以帮助您遵循“单一责任原则”并使代码更具可测试性。Rust和Go没有继承(它们也没有类),但是我不认为代码比其他OOP语言更冗余。

此外,如果使用组合,您自然会发现使用接口可以使代码具有所需的结构和灵活性(请参见有关接口用例的其他答案)。

注意:您可以与Java 8接口共享代码

最后,最后一个报价:

在令人难忘的问答环节中,有人问他[James Gosling]:“如果您可以再次使用Java,您将作何改变?” “我会放弃上课”(在网上的任何地方,都不知道这是不是真的)


3

继承是指一个类是从另一个类(可以是抽象的)或接口派生而来的。面向对象(继承性)的最强之处不是代码的重用(有很多方法可以做到),而是多态。

多态性是当您拥有使用该接口的代码时,它的实例对象可以是从该接口派生的任何类。例如,我可以有一个这样的方法:public void Pet(IAnimal animal)并且此方法将获得一个对象,该对象是从IAnimal继承的Dog或Cat的实例。或者我可以有这样的代码:IAnimal animal,然后我可以调用此接口的方法:animal.Eat(),Dog或Cat可以用不同的方式实现。

接口的主要优点是可以从某些接口继承,但是如果只需要从一个接口继承,则也可以使用抽象类。这里是一篇文章,详细解释了抽象类和接口之间的区别:http : //www.codeproject.com/KB/cs/abstractsvsinterfaces.aspx


2

两种方法都有效(接口和多重继承)。

快速实用的简短答案

如果您有多年使用多重继承的经验,而这些继承具有仅方法定义,完全没有代码的超级类,则接口会更好。

一个补充的问题可能是:“如何以及为什么将代码从抽象类迁移到接口”。

如果您在应用程序中没有使用很多抽象类,或者您没有太多的使用经验,那么您可能希望跳过接口。

不要急于使用接口。

长无聊的答案

接口非常相似,甚至等同于抽象类。

如果您的代码有许多Abstract类,那么您就该开始考虑接口了。

以下代码与抽象类:


MyStreamsClasses.java

/* File name : MyStreamsClasses.java */
import java.lang.*;
// Any number of import statements

public abstract class InputStream {
  public void ReadObject(Object MyObject);
}

public abstract class OutputStream {
  public void WriteObject(Object MyObject);
}

public abstract class InputOutputStream 
    imnplements InputStream, OutputStream {
  public void DoSomethingElse();
}

可以替换为:


MyStreamsInterfaces.java

/* File name : MyStreamsInterfaces.java */
import java.lang.*;
// Any number of import statements

public interface InputStream {
  public void ReadObject(Object MyObject);
}

public interface OutputStream {
  public void WriteObject(Object MyObject);
}

public interface InputOutputStream 
    extends InputStream, OutputStream {
  public void DoSomethingElse();
}

干杯。


0

介面

接口是定义如何与对象交互的协定。它们对于表达内部构件如何与对象交互很有用。继依赖反转的公共API将有接口的表示所有参数。您不在乎它如何执行所需的操作,而只是在它确实满足您的要求即可。

示例:您可能只需要Vehicle运输货物,而不用担心特定的运输方式。

遗产

继承是特定实现的扩展。该实现可能满足也可能不满足特定的接口。仅当您关心如何实现时,才应期望特定实现的祖先。

示例:您可能需要Plane实施车辆以实现快速运输。

组成

合成可以用作继承的替代方法。它不是使用您的类扩展基类,而是使用实现主类职责较小部分的对象创建的。在facade pattern和中使用了组合decorator pattern

例如:你可能创建DuckBoatDUKW实现)类LandVehicleWaterVehicle其中两个工具Vehicle组成TruckBoat实施方式。

答案

Q1。由于接口仅具有抽象方法(无代码),因此,我们如何说如果实现任何接口,那么它就是继承?我们没有使用它的代码。

接口不是继承。实施接口表示您打算让类按照接口定义的方式进行操作。继承是指您有一个共同的祖先,并且您收到与祖先相同的行为(inherit),因此无需定义它。

Q2。如果实现接口不是继承,那么如何使用接口实现多重继承?

接口不实现多重继承。他们表示,一门课程可能适合多个职位。

Q3。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在实现它的所有类中一次又一次地编写代码。

接口的主要优点之一是提供关注点分离:

  • 您可以编写一个与其他类一起执行某些操作的类,而无需关心该类的实现方式。
  • 任何将来的开发都可以与您的实现兼容,而无需扩展特定的基类。

本着DRY您的精神,您可以编写一个满足接口要求的实现,并在尊重使用open/closed principal组合的同时更改它。


如果您可以更多地了解方法,而不是这样Interfaces are not inheritance.,将对您有所帮助,这样可以帮助打破我作为菜鸟仍难以看到不同之处的定义。任何人,谢谢您的解释。
罗杰

@ Kim.LBy感谢您的输入。这样澄清起来更好吗?
史蒂夫·布佐纳斯

是的,现在更清楚了。编辑后的答案显示了具体类从其父类而不是从接口继承的值。
罗杰'18

0

Q1。由于接口仅具有抽象方法(无代码),因此,如果我们实现接口,那么该如何说是继承呢?我们没有使用它的代码。

它不是平等的继承。这只是相似的。让我解释:

VolvoV3 extends VolvoV2, and VolvoV2 extends    Volvo (Class)
VolvoV3 extends VolvoV2, and VolvoV2 implements Volvo (Interface)

line1: Volvo v = new VolvoV2(); 
line2: Volvo v = new VolvoV3(); 

如果仅看到line1和line2,则可以推断VolvoV2和VolvoV3具有相同的类型。您无法推断Volvo是超类还是Volvo是接口。

Q2。如果实现接口不是继承,那么如何使用接口实现多重继承?

现在使用接口:

VolvoXC90 implements XCModel and Volvo (Interface)
VolvoXC95 implements XCModel and Volvo (Interface)

line1: Volvo   a = new VolvoXC90();
line2: Volvo   a = new VolvoXC95();
line3: XCModel a = new VolvoXC95();

如果仅看到line1和line2,则可以推断VolvoXC90和VolvoXC95具有相同的类型(Volvo)。您不能推断沃尔沃是超类或沃尔沃是接口。

如果仅看到line2和line3,则可以推断Volvo95实现两种类型,即XCModel和Volvo,在Java中,您知道至少一个必须是接口。例如,如果此代码是用C ++编写的,则它们可能都是两个类。因此,多重继承。

Q3。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在实现它的所有类中一次又一次地编写代码。

想象一个系统,您在其他200个类中使用VolvoXC90类。

VolvoXC90 v = new VolvoXC90();

如果您需要改进系统以启动VolvoXC95,则必须更改200个其他类。

现在,想象一个系统,在其中使用10,000,000类的Volvo接口。

// Create VolvoXC90 but now we need to create VolvoXC95
Volvo v = new VolvoFactory().newCurrentVolvoModel(); 

现在,如果您需要改进系统以创建VolvoXC95模型,则只需更改一个类,即Factory。

这是一个常识性的问题。如果您的系统仅由很少的类组成并且更新很少,则使用随处可见的接口会适得其反。对于大型系统,它可以为您省去很多麻烦,并避免采用接口的风险。

我建议您阅读有关SOLID原理的更多信息,并阅读有效的Java书。它从经验丰富的软件工程师那里汲取了很好的教训。


0

进行接口以便类可以实现接口内的功能并按照该接口运行。


0

所以。这里有很多很好的答案,详细解释了什么是接口。然而,这只是它使用的一个例子,就像几年前我最好的一位同事向我解释的那样,过去几年我在大学里学到的东西混杂在一起。

界面是一种“合同”。它公开了一些可用的方法,字段等。它不显示任何实现细节,仅显示返回的内容和使用的参数。这是第三个问题的答案,我认为这是现代OOP的最大优势之一:

“通过加法而不是修改的代码” -AAU Magnus Madsen

至少他就是这样称呼的,他可能会从其他地方得到它。下面的示例代码是用C#编写的,但是显示的所有内容几乎都可以用Java相同的方式完成。

我们看到的是一个名为SampleApp的类,它具有单个字段IOContext。IOContext是一个接口。SampleApp并不关心如何保存数据,只需要在其“ doSomething()”方法中这样做即可。

我们可以想象,在开发过程的开始,保存数据可能比保存数据的方式更为重要,因此,开发人员选择简单地编写FileContext类。但是后来,无论出于何种原因,他都需要支持JSON。因此,他编写了JSONFileContext类,该类继承了FileContext。这意味着它实际上是一个具有FileContext功能的IOContext,保存了FileContexts SaveData和LoadData的替换,它仍然使用其“写入/读取”方法。

与编写JSON类并使其继承IOContext相比,实现JSON类的工作量很小。

SampleApp的字段本来可以是'FileContext'类型,但是那样一来,它将仅限于仅使用该类的子级。通过创建接口,我们甚至可以执行SQLiteContext实现,并将其写入数据库,SampleApp将永远不会知道或关心,并且当我们编写sql lite类时,我们只需要对代码进行一次更改:new JSONFileContext();而不是成为new SQLiteContext();

我们仍然有旧的实现,如果需要,可以切换回原来的实现。我们什么都没破坏,对代码的所有更改只有半行,可以在眨眼之间更改。

所以:用加法而不是修改来编码。

namespace Sample
{
    class SampleApp
    {
        private IOContext context;

        public SampleApp()
        {
            this.context = new JSONFileContext(); //or any of the other implementations
        }

        public void doSomething()
        {
            //This app can now use the context, completely agnostic of the actual implementation details.
            object data = context.LoadData();
            //manipulate data
            context.SaveData(data);
        }
    }

    interface IOContext
    {
        void SaveData(object data);
        object LoadData();
    }

    class FileContext : IOContext
    {
        public object LoadData()
        {

            object data = null;
            var fileContents = loadFileContents();
            //Logic to turn fileContents into a data object
            return data;
        }

        public void SaveData(object data)
        {
            //logic to create filecontents from 'data'
            writeFileContents(string.Empty);
        }

        protected void writeFileContents(string fileContents)
        {
            //writes the fileContents to disk
        }

        protected string loadFileContents()
        {
            string fileContents = string.Empty;
            //loads the fileContents and returns it as a string
            return fileContents;
        }
    }

    class JSONFileContext : FileContext
    {
        public new void SaveData(object data)
        {
            //logic to create filecontents from 'data'
            base.writeFileContents(string.Empty);
        }

        public new object LoadData()
        {
            object data = null;
            var fileContents = loadFileContents();
            //Logic to turn fileContents into a data object
            return data;
        }
    }

    class SQLiteContext : IOContext
    {
        public object LoadData()
        {
            object data = null;
            //logic to read data into the data object
            return data;
        }

        public void SaveData(object data)
        {
            //logic to save the data object in the database
        }
    }
}
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.