为啥要使用接口,多重继承与接口,接口的好处?
Posted
技术标签:
【中文标题】为啥要使用接口,多重继承与接口,接口的好处?【英文标题】:Why to use Interfaces, Multiple Inheritance vs Interfaces, Benefits of Interfaces?为什么要使用接口,多重继承与接口,接口的好处? 【发布时间】:2012-01-21 19:26:37 【问题描述】:我仍然对这件事有些困惑。我到现在发现的是
(这里已经提出了类似的问题,但我还有其他一些问题。)
接口是仅抽象方法和最终字段的集合。
Java 中没有多重继承。
接口可用于在Java中实现多重继承。
继承的一个优点是我们可以在派生类中使用基类的代码,而无需重新编写它。可能这是继承存在的最重要的事情。
现在..
第一季度。由于接口只有抽象方法(没有代码),所以我们怎么能说如果我们正在实现任何接口,那么它就是继承?我们没有使用它的代码。
第二季度。如果实现接口不是继承,那么接口是如何实现多重继承的呢?
第三季度。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在我们实现它的所有类中一次又一次地编写代码。
那为什么要做接口呢?
注意:我发现了一种接口很有帮助的情况。一个例子是在 Runnable 接口中,我们有 public void run() 方法,在该方法中我们定义了线程的功能,并且内置编码该方法将作为单独的线程运行。所以我们只需要在线程中编写代码,Rest 是预定义的。但这件事也可以使用抽象类来实现。
那么使用接口的确切好处是什么?我们使用接口实现的真的是多重继承吗?
【问题讨论】:
【参考方案1】:第一季度。由于接口只有抽象方法(没有代码),所以我们怎么能说如果我们正在实现任何接口,那么它就是继承?我们没有使用它的代码。
我们不能。接口不用于实现多重继承。他们将其替换为更安全但功能稍弱的构造。注意关键字implements
而不是extends
。
第二季度。如果实现接口不是继承,那么接口是如何实现多重继承的呢?
他们不是。使用接口,单个类可以有多个“视图”、不同的 API 或功能。例如。一个类可以同时是Runnable
和Callable
,而这两种方法实际上是在做同样的事情。
第三季度。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在我们实现它的所有类中一次又一次地编写代码。
接口是一种多重继承,没有后者引入的问题(如Diamond problem)。
接口的用例很少:
对象实际上有两个身份:Tank
是Vehicle
和Weapon
。您可以使用Tank
的实例,其中预期前者或后者(多态性)。这在现实生活中很少见,实际上是多继承更好(或特征)的有效示例。
简单职责:游戏中Tank
对象的实例也是Runnable
,让你在线程中执行它,ActionListener
响应鼠标事件。
回调接口:如果对象实现给定的回调接口,它会被通知其生命周期或其他事件。
标记接口:不添加任何方法,但可通过instanceof
轻松访问以发现对象功能或愿望。 Serializable
和 Cloneable
就是这样的例子。
您正在寻找的是 trait(如在 Scala 中),不幸的是在 Java 中不可用。
【讨论】:
您能详细说明使用接口的好处吗? @GPSingh:我尝试在Q3
下方添加几点。您还有其他问题吗?
问题二的答案 - “它们不是”用于实现多重继承,与问题三的答案 - “接口是一种多重继承”相矛盾。这让人有些困惑。
在Q1:你说它不是用来实现多重继承的,但是在Q2:如果你说的是那种多重继承。你能说得更具体更清楚吗?【参考方案2】:
接口是最终静态字段和抽象方法的集合(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
根据上面提到的伪代码,任何能够吃或睡的东西都称为动物,或者我们可以说所有动物都必须吃和睡,但吃和睡的方式取决于动物。
在接口的情况下,我们只继承行为,而不是在类继承的情况下实际代码。
第一季度。由于接口只有抽象方法(没有代码),所以我们怎么能说如果我们正在实现任何接口,那么它就是继承?我们没有使用它的代码。
实现接口是另一种继承。它与类的继承不同,因为继承子类从基类中获取要重用的真实代码。
第二季度。如果实现接口不是继承,那么接口是如何实现多重继承的呢?
据说是因为一个类可以实现多个接口。但我们需要明白,这种继承不同于类的继承。
第三季度。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在我们实现它的所有类中一次又一次地编写代码。
实现一个接口会强制类必须重写其所有抽象方法。
在我的书中阅读更多内容here 和 here
【讨论】:
我觉得你在代码中构建的结构使用扩展继承会比使用实现接口更好、更合乎逻辑。我认为这是一个相当糟糕的例子 @gprathour 我同意,所以请提供一个更好的例子:) 如果你想要一个更实际的例子来说明什么时候使用接口,什么时候不使用。考虑编写一个必须保存一些数据的应用程序。一开始你知道你会想要保存数据,然后再次加载它,但你可能会对如何进行冲突:因此你创建了一个 IOInterface。您首先选择将数据写入普通文件,然后创建继承 IOInterface 的 FileContext。然后你发现将它保存在 SQLite 数据库中实际上是这样的。然后你可以编写一个继承 IOInterface 的 SQLiteContext。代码是添加的,而不是删除的,它仍然兼容。加法编码。 更好的方法是将动物作为父类。猴子和狮子会延伸动物。会有一个由 Monkey 实现的接口 Swing,一个由 Lion 实现的接口 Roar。 @MobDev 这里的重点是要详细说明两个不同的类对于同一个接口的方法可以有自己不同的实现。类必须重写方法,但它们可以有不同的实现代码。【参考方案3】:第一季度。由于接口只有抽象方法(没有代码),所以我们怎么能说如果我们正在实现任何接口,那么它就是继承?我们没有使用它的代码。
不幸的是,在口语用法中,inheritance
这个词在类实现接口时仍然经常使用,尽管interface implementation
是一个更可取的术语 - IMO,术语inheritance
应该严格用于继承具体或抽象类。在 C++ 和 C# 等语言中,相同的语法(即 Subclass : Superclass
和 Class : Interface
)用于类继承和接口实现,这可能导致了将 inheritance
一词与接口误用的传播。 Java 对 extend
一个类和 implement
一个接口有不同的语法,这是一件好事。
Q2 如果实现接口不是继承,那么接口是如何实现多重继承的呢?
您可以通过组合实现多重继承的“效果”——通过在一个类上实现多个接口,然后为该类上所有接口所需的所有方法、属性和事件提供实现。对具体类执行此操作的一种常见技术是通过将实现“连接”到每个内部类实现来实现与实现外部接口的类之间的“具有”(组合)关系。 (诸如 C++ 之类的语言确实直接支持多重具体继承,但这会产生其他潜在问题,例如菱形问题。
Q3 无论如何,使用接口有什么好处?他们没有任何代码。我们需要在我们实现它的所有类中一次又一次地编写代码。
接口允许现有类(例如框架)与您的新类进行交互,而之前从未“见过”它们,因为它能够通过已知接口进行通信。将接口视为合同。通过在一个类上实现这个接口,你有义务履行它所要求的义务,一旦这个契约被实现,那么你的类应该能够与使用该接口的任何其他代码互换使用。
现实世界的例子
“现实世界”的示例是特定国家/地区围绕墙壁电源插座的立法和惯例(接口)。插入插座的每个电器都需要符合当局为插座定义的规格(合同),例如线、零线和地线的位置,开/关开关的位置和颜色,以及在打开interface
时将提供的电压、频率和最大电流的一致性。
将接口(即标准壁式插座)去耦而不只是将电线焊接在一起的好处是,您可以插入(和拔出)风扇、水壶、双适配器或一些接下来要发明的新设备一年过去了,尽管在设计界面时这个设备并不存在。为什么?因为它会符合接口的要求。
为什么要使用接口?
接口非常适合类的松散耦合,并且是 Bob 大叔 SOLID 范式的支柱之一,尤其是 Dependency Inversion Principle
和 Interface Segregation Principles
。
简单地说,通过确保类之间的依赖关系只耦合在接口(抽象)上,而不是在其他具体类上,它允许将依赖关系替换为满足接口要求的任何其他类实现。
在测试中,可以使用依赖项的存根和模拟来对每个类进行单元测试,并且可以“窥探”类与依赖项的交互。
【讨论】:
您首先要明确interface implementation
和concrete inheritance
,这是一个非常重要的区别。但是您后面对术语interface
的使用在现实世界的示例中可能会令人困惑。可以做些什么来提高清晰度吗?例如。现实世界的例子是什么?接口,继承?下一个短语可能会让人们相信它是一个接口示例,但该示例似乎是一个具体的实现。
谢谢史蒂夫 - 你是对的,我的回答需要一些 TLC。【参考方案4】:
亲吻
我已经搜索了几天,甚至几周都在尝试理解接口,并且似乎阅读了相同的通用帮助;我并不是要贬低这些贡献,但我认为灯泡只是点击了一下,所以我很高兴:))
我更喜欢保持简单愚蠢,因此将提供我新发现的界面视图。
我是一个普通的程序员,但我想把这段我用VB.NET写的代码贴出来(其他语言的原理相同),以帮助其他人理解接口。
如果我错了,请在后续 cmets 中告诉其他人。
说明
表单上的三个按钮,单击每个按钮都会保存对接口变量(_data)的不同类引用。不同类引用到接口变量的全部意义,是我不明白的,因为它似乎是多余的,然后它的力量在 msgbox 中变得明显,我只需要调用 SAME 方法来执行我需要的任务,在这个case 'GetData()',它使用接口引用变量(_data)当前持有的类中的方法。
因此,无论我希望获取我的数据(从数据库、网络还是文本文件),都只能使用 相同的方法名称;该实现背后的代码...我不在乎。
然后可以轻松地使用接口更改每个类代码,而无需任何依赖...这是 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
【参考方案5】:
老问题。我很惊讶没有人引用规范的资料:Java: an Overview by James Gosling,Design Patterns: Elements of Reusable Object-Oriented Software by the Gang of Four 或 Effective Java 作者 Joshua Bloch(以及其他来源)。
我将从引用开始:
接口只是对象响应的一组方法的规范。它不包括任何实例变量或实现。接口可以被多重继承(与类不同),并且它们可以以比通常的刚性类更灵活的方式使用 继承结构。 (高斯林,第 8 页)
现在,让我们一一提出您的假设和问题(我将自愿忽略 Java 8 的特性)。
假设
接口是仅抽象方法和最终字段的集合。
您在 Java 接口中看到关键字abstract
了吗?不,那么您不应该将接口视为抽象方法的集合。也许你被 C++ 所谓的接口误导了,这些接口是只有纯虚方法的类。 C++ 在设计上没有(也不需要)接口,因为它具有多重继承。
正如 Gosling 所解释的,您应该将接口视为“对象响应的一组方法”。我喜欢将界面和相关文档视为服务合同。它描述了您可以从实现该接口的对象中获得什么。文档应指定前置条件和后置条件(例如,参数不应为空,输出始终为正,...)和不变量(不修改对象内部状态的方法)。我认为,这份合同是 OOP 的核心。
Java 中没有多重继承。
确实。
JAVA 省略了 C++ 中许多很少使用、难以理解、令人困惑的特性,根据我们的经验,这些特性带来的痛苦多于好处。这主要包括运算符重载(尽管它确实有方法重载)、多重继承和广泛的自动强制。 (高斯林,第 2 页)
没什么可补充的。
接口可用于在Java中实现多重继承。
不,simlpy,因为 Java 中没有多重继承。见上文。
继承的一个优点是我们可以在派生类中使用基类的代码,而无需重新编写。可能这是继承存在的最重要的事情。
这就是所谓的“实现继承”。正如您所写,这是一种重用代码的便捷方式。
但它有一个重要的对应物:
父类通常至少定义其子类的部分物理表示。因为继承将子类暴露给其父类的实现细节,所以人们常说“继承破坏了封装”[Sny86]。子类的实现与其父类的实现如此紧密地联系在一起,以至于父类实现的任何改变都会迫使子类改变。 (GOF,1.6)
(在 Bloch 第 16 条中有类似的引用。)
其实继承还有另一个目的:
类继承结合了接口继承和实现继承。接口继承定义了一个新的接口 或更多现有接口。实现继承根据一个或多个现有实现定义了一个新的实现。 (GOF,附录 A)
两者都在 Java 中使用关键字 extends
。您可能有类的层次结构和接口的层次结构。第一个分担实施,第二个分担义务。
问题
Q1。由于接口只有抽象方法(没有代码),所以我们怎么能说如果我们正在实现任何接口,那么它就是继承?我们没有使用它的代码。**
接口的实现不是继承。是执行。因此关键字implements
。
Q2。如果实现一个接口不是继承,那么接口是如何实现多重继承的呢?**
Java 中没有多重继承。见上文。
Q3。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在我们实现它的所有类中一次又一次地编写代码。/那为什么要制作接口?/使用接口的确切好处是什么?我们使用接口实现的真的是多重继承吗?
最重要的问题是:你为什么想要多重继承?我可以想到两个答案:1.给一个对象多个类型; 2. 重用代码。
为一个对象赋予多种类型
在 OOP 中,一个对象可能有不同的类型。例如在 Java 中,ArrayList<E>
具有以下类型:Serializable
、Cloneable
、Iterable<E>
、Collection<E>
、List<E>
、RandomAccess
、AbstractList<E>
、AbstractCollection<E>
和 @9876543我希望我没有忘记任何人)。如果一个对象有不同的类型,那么不同的消费者将能够在不知道它的特殊性的情况下使用它。我需要一个Iterable<E>
而你给我一个ArrayList<E>
?没关系。但如果我现在需要List<E>
而你给我ArrayList<E>
,也可以。等等。
如何在 OOP 中键入对象?你以Runnable
接口为例,这个例子完美的说明了这个问题的答案。我引用官方 Java 文档:
此外,Runnable 提供了使类处于活动状态而不是子类化 Thread 的方法。
重点是:继承是键入对象的便捷方式。您想创建一个线程吗?让我们继承Thread
类。您希望一个对象具有不同的类型,让我们使用多重继承。啊。它在 Java 中不存在。 (在 C++ 中,如果你想让一个对象有不同的类型,多继承是要走的路。)
那么如何为一个对象赋予多种类型呢?在Java 中,您可以直接 键入您的对象。这就是当你的类implements
Runnable
接口时你所做的。如果您喜欢继承,为什么要使用Runnable
?也许是因为你的类已经是另一个类的子类,比如说A
。现在你的班级有两种类型:A
和 Runnable
。
通过多个接口,您可以为一个对象赋予多种类型。你只需要创建一个implements
多个接口的类。只要您遵守合同,就可以。
重用代码
这是一个困难的主题;我已经引用了 GOF 关于打破封装的内容。其他答案提到了钻石问题。你也可以想到单一职责原则:
一个班级应该只有一个改变的理由。 (Robert C. Martin,敏捷软件开发、原则、模式和实践)
拥有一个父类可能会给一个类一个改变的理由,除了它自己的责任:
超类的实现可能会随着版本的变化而变化,如果发生这种情况,子类可能会中断,即使它的代码没有被触及。因此,子类必须与其超类同步发展(Bloch,第 16 条)。
我要补充一个更平淡的问题:当我试图在一个类中找到一个方法的源代码时,我总是有一种奇怪的感觉,但我找不到它。然后我记得:它必须在父类的某个地方定义。或者在祖父母班。或者甚至更高。在这种情况下,一个好的 IDE 是一项宝贵的资产,但在我看来,它仍然是一种神奇的东西。与接口层次结构没有什么相似之处,因为 javadoc 是我唯一需要的东西:IDE 中的一个键盘快捷键,我就明白了。
继承方式有优势:
在包中使用继承是安全的,其中子类和超类的实现都在同一个程序员的控制之下。在扩展专门为扩展设计和记录的类时,使用继承也是安全的(第 17 条:为继承设计和记录,否则禁止它)。 (布洛赫,第 16 条)
AbstractList
是 Java 中“专门为扩展而设计和记录的”类的一个示例。
但是 Bloch 和 GOF 坚持这一点:“偏好组合胜于继承”:
委托是一种让组合像继承一样强大的复用方式 [Lie86, JZ91]。在委托中,处理请求涉及两个对象:接收对象将操作委托给它的委托。这类似于子类将请求推迟到父类。 (GOF 第 32 页)
如果您使用组合,则不必一次又一次地编写相同的代码。您只需创建一个处理重复的类,然后将此类的一个实例传递给这些类实现接口。这是重用代码的一种非常简单的方法。这有助于您遵循单一职责原则并使代码更具可测试性。 Rust 和 Go 没有继承(它们也没有类),但我不认为代码比其他 OOP 语言更冗余。
此外,如果您使用组合,您会发现自己很自然地使用接口来为您的代码提供所需的结构和灵活性(请参阅有关接口用例的其他答案)。
注意:您可以使用 Java 8 接口共享代码
最后,最后一句:
在令人难忘的问答环节中,有人问他 [James Gosling]:“如果你可以重做 Java,你会改变什么?” “我会遗漏课程”(网络上的任何地方,不知道这是不是真的)
【讨论】:
【参考方案6】:这是一个非常古老的问题,java-8 版本为界面添加了更多功能和功能。
接口声明可以包含
-
方法签名
默认方法
静态方法
常量定义。
在接口中实现的唯一方法是 default 和 static 方法。
界面使用:
-
定义合同
链接不相关的类具有 capabilities(例如,实现 Serializable 接口的类之间可能有也可能没有任何关系,除了实现该接口
提供可互换实施,例如
Strategy_pattern
默认方法使您能够向库的接口添加新功能,并确保二进制兼容性与为这些接口的旧版本编写的代码
使用静态方法组织库中的辅助方法(您可以将特定于接口的静态方法保留在同一接口中,而不是在单独的类中)
查看这个相关的 SE 问题以获取代码示例以更好地理解这些概念:
How should I have explained the difference between an Interface and an Abstract class?
回到您的查询:
第一季度。由于接口只有抽象方法(没有代码),所以我们怎么能说如果我们正在实现任何接口,那么它就是继承?我们没有使用它的代码。
第二季度。如果实现接口不是继承,那么接口是如何实现多重继承的呢?
接口可以包含 static 和 default 方法的代码。这些默认方法提供向后兼容性,而静态方法提供 helper/utility 功能。
你不能在java中拥有真正的多重继承,接口不是获取它的方法。接口只能包含常量。所以你不能继承状态,但你可以实现行为。
您可以将 inheritance 替换为 capability。 接口为实现类提供了多种功能。
第三季度。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在我们实现它的所有类中一次又一次地编写代码。
请参阅我的回答中的“界面使用”部分。
【讨论】:
【参考方案7】:继承是指一个类派生自另一个类(可以是抽象类)或接口。面向对象(继承)的最强点不是代码的重用(有很多方法可以做到),而是多态。
多态性是指您有使用接口的代码,它的实例对象可以是从该接口派生的任何类。例如我可以有这样的方法: public void Pet(IAnimal animal) 并且此方法将获取一个对象,该对象是继承自 IAnimal 的 Dog 或 Cat 的实例。或者我可以有这样的代码: I动物 然后我可以调用这个接口的一个方法: Animal.Eat() Dog 或 Cat 可以以不同的方式实现。
接口的主要优点是您可以从其中一些继承,但如果您只需要从其中一个继承,您也可以使用抽象类。这是一篇文章,它更多地解释了抽象类和接口之间的区别: http://www.codeproject.com/KB/cs/abstractsvsinterfaces.aspx
【讨论】:
【参考方案8】:两种方法都有效(接口和多重继承)。
快速实用的简短回答
如果您有多年使用多重继承的经验,并且具有仅具有方法定义且根本没有代码的超类,则接口会更好。
一个补充问题可能是:“如何以及为什么将代码从抽象类迁移到接口”。
如果您在应用程序中使用的抽象类不多,或者您没有太多经验,您可能更愿意跳过接口。
不要急于使用接口。
冗长无聊的答案
接口非常相似,甚至等同于抽象类。
如果您的代码有许多抽象类,那么您应该开始考虑接口。
以下带有抽象类的代码:
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();
干杯。
【讨论】:
【参考方案9】:所以。这里有很多很好的答案,详细解释了接口是什么。然而,这是它使用的一个例子,就像我最好的同事之一多年前向我解释过的那样,与我在过去几年在大学里学到的东西混在一起。
接口是一种“契约”。它公开了一些可用的方法、字段等。它没有透露任何实现细节,只透露它返回的内容以及它采用的参数。这就是问题三的答案,我认为这是现代 OOP 的最大优势之一:
“代码添加,而不是修改” - Magnus Madsen,AAU
至少他是这么称呼它的,而且他可能从其他地方得到它。下面的示例代码是用 C# 编写的,但显示的所有内容都可以在 Java 中以几乎相同的方式完成。
我们看到的是一个名为 SampleApp 的类,它有一个字段,即 IOContext。 IOContext 是一个接口。 SampleApp 并不关心它如何保存数据,它只需要在它的“doSomething()”方法中这样做。
我们可以想象,在开发过程的开始,保存数据可能比如何保存数据更重要,所以开发者选择了简单地编写 FileContext 类。然而,后来,无论出于何种原因,他都需要支持 JSON。于是他写了JSONFileContext类,继承了FileContext。这意味着它实际上是一个 IOContext,它具有 FileContext 的功能,省去了 FileContexts SaveData 和 LoadData 的替换,它仍然使用它的 'write/read' 方法。
与编写类并让它只继承 IOContext 相比,实现 JSON 类的工作量很小。
SampleApp 的字段可能只是“FileContext”类型,但这样一来,它就会被限制为只能使用该类的子类。通过制作接口,我们甚至可以实现 SQLiteContext 并写入数据库,SampleApp 永远不会知道或关心,当我们编写了 SQL lite 类时,我们只需要对我们的代码进行一次更改:new JSONFileContext();
变成new SQLiteContext();
我们仍然有旧的实现,如果需要,可以切换回那些。我们没有破坏任何东西,我们代码的所有更改都是半行,可以在眨眼之间改回来。
so:添加代码,而不是修改代码。
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
【讨论】:
【参考方案10】:接口
接口是定义如何与对象交互的契约。它们对于表达你的内部打算如何与对象交互很有用。在依赖倒置之后,您的公共 API 将使用接口表示所有参数。你不关心它如何做你需要它做的事情,只关心它完全做你需要它做的事情。
示例:您可能只需要Vehicle
来运输货物,您并不关心特定的运输方式。
继承
继承是特定实现的扩展。该实现可能满足也可能不满足特定接口。只有当您关心如何实现时,您才应该期待特定实现的祖先。
示例:您可能需要 Plane
实现车辆以进行快速运输。
作曲
组合可以用作继承的替代方法。它不是您的类扩展基类,而是使用实现主类责任的较小部分的对象创建的。组合用于facade pattern
和decorator pattern
。
示例:您可以创建一个实现LandVehicle
和WaterVehicle
的DuckBoat
(DUKW) 类,它们都实现了由Truck
和Boat
实现组成的Vehicle
.
答案
第一季度。由于接口只有抽象方法(没有代码),所以我们怎么能说如果我们正在实现任何接口,那么它就是继承?我们没有使用它的代码。
接口不是继承。实现接口表示您打算让您的类以接口定义的方式运行。继承是指您有一个共同的祖先,并且您收到与祖先相同的行为 (inherit
),因此您不需要定义它。
第二季度。如果实现接口不是继承,那么接口是如何实现多重继承的呢?
接口不实现多重继承。他们表示一个类可能适合多个角色。
第三季度。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在我们实现它的所有类中一次又一次地编写代码。
接口的主要好处之一是提供关注点分离:
您可以编写一个与另一个类一起做某事的类,而无需关心该类是如何实现的。 未来的任何开发都可以与您的实现兼容,而无需扩展特定的基类。本着DRY
的精神,您可以编写一个满足接口的实现并对其进行更改,同时仍然尊重open/closed principal
,如果您利用组合的话。
【讨论】:
如果你能更多地了解如何而不是Interfaces are not inheritance.
,这将有助于打破我作为菜鸟仍然难以看到不同和相似之处的定义。任何人,谢谢你的解释。
@Kim.LBy 感谢您的意见。这样更清楚吗?
是的,现在更清楚了。编辑后的答案显示了具体类从其父类而不是从接口继承的值。【参考方案11】:
第一季度。由于接口只有抽象方法(没有代码),所以我们怎么能说如果我们正在实现一个接口,那么它就是继承?我们没有使用它的代码。
这不是平等的继承。这只是相似的。让我解释一下:
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 具有相同的类型。您无法推断沃尔沃是超类还是沃尔沃是接口。
第二季度。如果实现接口不是继承,那么如何使用接口实现多重继承?
现在使用接口:
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 具有相同的类型(沃尔沃)。你不能推断沃尔沃是一个超类或沃尔沃是一个接口。
如果你只看到 line2 和 line3,你可以推断出 Volvo95 实现了两种类型,XCModel 和 Volvo,在 Java 中你知道至少一个必须是接口。例如,如果此代码是用 C++ 编写的,它们可能都是两个类。因此,多重继承。
第三季度。无论如何,使用接口有什么好处?他们没有任何代码。我们需要在我们实现它的所有类中一次又一次地编写代码。
想象一个系统,其中您在 200 个其他类中使用了一个 VolvoXC90 类。
VolvoXC90 v = new VolvoXC90();
如果您需要改进系统以启动 VolvoXC95,您必须更改 200 个其他类。
现在,想象一个系统,其中您在 10,000,000 个类中使用沃尔沃接口。
// Create VolvoXC90 but now we need to create VolvoXC95
Volvo v = new VolvoFactory().newCurrentVolvoModel();
现在,如果您需要改进系统以创建 VolvoXC95 模型,您只需更改一个类,即工厂。
这是一个常识问题。如果您的系统仅由几个类组成并且几乎没有更新,那么到处使用接口会适得其反。对于大型系统,它可以为您节省很多痛苦并避免采用接口的风险。
我建议您阅读有关 S.O.L.I.D 原则的更多信息并阅读《有效 Java》一书。它从经验丰富的软件工程师那里得到了很好的教训。
【讨论】:
【参考方案12】:创建接口是为了让一个类实现接口内的功能并按照该接口运行。
【讨论】:
以上是关于为啥要使用接口,多重继承与接口,接口的好处?的主要内容,如果未能解决你的问题,请参考以下文章