“按接口编程”是什么意思?

920
我在几次提到过这个,但不清楚它是什么意思。你什么时候和为什么要这样做?
我知道接口是做什么的,但因为我不太清楚这一点,所以我认为我可能没有正确地使用它们。
这只是为了让你能够执行以下操作吗?
IInterface classRef = new ObjectWhatever()

你可以使用任何实现了 IInterface 接口的类?那么什么情况下需要这样做呢?我所能想到的唯一情形是,当你有一个方法并且不确定将传递哪个对象,除非它实现了 IInterface 接口。我认为这种情况并不经常出现。

另外,你如何编写一个接收实现某个接口的对象作为参数的方法呢?这是可能的吗?


4
如果你记得的话,并且你的程序需要优化,在编译之前,你可能希望交换接口声明与实际实现。因为使用接口会增加间接性,从而影响性能。但是,建议将代码编写为基于接口编程的形式进行分发。 - Ande Turner
25
@Ande Turner:那是很糟糕的建议。1)“你的程序需要是最优的”不是替换接口的好理由!然后你说“通过接口编写代码…”,所以你建议在满足要求(1)的情况下发布次优代码吗?!?Translated: @Ande Turner: 那是差劲的建议。1)“你的程序需要是最优的”不是替换接口的好理由!然后你说“通过接口编写代码…”,所以你建议在满足需求(1)的情况下发布次优代码吗?!? - Mitch Wheat
82
这里大部分答案都不太正确。它根本不意味着“使用interface关键字”。接口是使用某物的规范,与合同(请查阅)相同。除此之外的是实现,即如何履行该合同。只针对方法/类型的保证进行编程,以便在方法/类型以遵守合同的方式发生变化时,不会破坏使用它的代码。 - jyoungdev
2
@apollodude217,这实际上是整个页面上最好的答案。至少对于标题中的问题来说是这样,因为这里至少有3个非常不同的问题... - Andrew Spencer
7
这类问题的根本问题在于它假设“按照接口编程”意味着“将所有内容都封装在抽象接口中”,如果你考虑到这个术语早于Java风格的抽象接口概念,那么这种想法就是愚蠢的。 - Jonathan Allen
显示剩余9条评论
33个回答

3
所以,接口的优势在于我可以将方法的调用与任何特定类分离。而不是创建接口的实例,其中实现是由我选择实现该接口的任何类给出的。从而允许我拥有许多类,这些类具有类似但略有不同的功能,并且在某些情况下(与接口意图相关的情况)不关心它是哪个对象。
例如,我可以有一个移动接口。通过实现移动接口的任何对象(人、汽车、猫)都可以被传递并告知移动。而方法却无需知道类的类型。

3
C++解释。
将接口视为类的公共方法。
您可以创建一个依赖于这些公共方法的模板,以执行自己的函数(它调用类中定义的公共接口中的函数调用)。比如说,这个模板是一个容器,像一个Vector类,而它所依赖的接口是一个搜索算法。
定义了Vector所调用的函数/接口的任何算法类都将满足'合同'(正如原回复中所解释的那样)。算法甚至不需要属于相同的基类; 唯一的要求是Vector所依赖的函数/方法(接口)在您的算法中已定义。
所有这些的重点是您可以提供任何不同的搜索算法/类,只要它提供了Vector所依赖的接口(冒泡搜索、顺序搜索、快速搜索)即可。
您可能还想设计其他容器(列表、队列),通过使它们满足您的搜索算法所依赖的接口/合同来利用Vector相同的搜索算法。
这样做可以节省时间(面向对象编程原则'代码重用'),因为您只需编写一次算法,而无需针对每个新对象再次编写,同时又不会过度复杂化继承树问题。
至于错过的操作方式;在C++中至少是如此,因为这是大部分标准模板库的框架操作方式。
当然,使用继承和抽象类时,编程接口的方法论发生了变化; 但原则是相同的,您的公共函数/方法是您的类接口。
这是一个庞大的话题,也是设计模式的基石之一。

3
在Java中,这些具体类都实现了CharSequence接口:

CharBuffer、String、StringBuffer、StringBuilder

除了Object类以外,这些具体类没有一个共同的父类,因此它们之间没有关联性,除了它们都与字符数组有关,表示或操作字符数组。例如,一旦实例化一个String对象,就无法更改其字符,而StringBuffer或StringBuilder的字符可以进行编辑。
然而,每个类都能够适当地实现CharSequence接口方法:
char charAt(int index)
int length()
CharSequence subSequence(int start, int end)
String toString()

在某些情况下,原本接受String类型的Java类库类已经被修改以接受CharSequence接口。因此,如果您有一个StringBuilder实例,可以直接将StringBuilder本身作为参数传递,而不是提取一个String对象(这意味着需要实例化一个新对象)。
对于任何可以将字符附加到底层具体类对象实例的情况,一些类实现了Appendable接口,这也带来了类似的好处。所有这些具体类都实现了Appendable接口:
BufferedWriter、CharArrayWriter、CharBuffer、FileWriter、FilterWriter、LogStream、OutputStreamWriter、PipedWriter、PrintStream、PrintWriter、StringBuffer、StringBuilder、StringWriter和Writer。

很遗憾像CharSequence这样的接口太过单薄。我希望Java和.NET允许接口具有默认实现,这样人们就不会仅仅为了最小化样板代码而削减接口。鉴于任何合法的CharSequence实现,只需使用上述四种方法即可模拟String的大多数功能,但是许多实现可以以其他方式更高效地执行这些功能。不幸的是,即使特定的CharSequence实现将所有内容都保存在单个char[]中并且可以执行许多... - supercat
如果没有像 indexOf 这样的操作,调用者无法要求 CharSequence 执行此操作,而不是使用 charAt 检查每个单独的字符。即使调用者对特定的 CharSequence 实现不熟悉,也无法快速执行此操作。 - supercat

2

接口就像一个合同,您希望实现类实现合同(接口)中编写的方法。由于Java不提供多重继承,“面向接口编程”是实现多重继承的好方法。

如果您有一个已经扩展了其他类B的类A,但是您希望该类A也遵循某些指南或实现某个合同,则可以通过“面向接口编程”策略实现。


2
"面向接口编程"是一种哲学,而不是特定的语言结构或设计模式。它告诉你创建更好的软件系统的正确步骤顺序(例如,更具弹性、更易测试、更可扩展、更易扩展等等)。
实际上,它的意思是:
在跳入实现和编码之前(即 HOW),先考虑 WHAT:
- 系统应该包含哪些黑盒子 - 每个盒子的职责 - 每个客户端(也就是其他盒子、第三方盒子,甚至人类)与之通信的方式(每个盒子的API)
在明确了上述内容后,再去实现这些盒子(HOW)。
首先考虑盒子的定义和它的API,可以帮助开发者梳理出盒子的职责,并标记出其暴露的细节(即“API”)和隐藏的细节(即“实现细节”)之间的区别,这是非常重要的区分。
其中一个立竿见影且容易注意到的收益是,团队可以在不影响总体架构的情况下更改和改进实现。这也使得系统更易于测试(与TDD方法相符)。
除了上述特点外,这种方法还可以节省大量时间。无论是从单体应用到“无服务器”,从BE到FE,从OOP到函数式,面向接口编程都是一个很好的范例。
我强烈推荐这种方法用于软件工程(而且我基本上认为它在其他领域也完全有意义)。

2
"面向接口编程"是《设计模式》一书中的术语。它不仅与Java接口有关,也与真正的接口有关。为了实现清晰的层次分离,需要在系统之间创建一些分离,例如:假设您有一个具体的数据库要使用,您永远不会“针对数据库进行编程”,而是会“针对存储接口进行编程”。同样,您永远不会“针对Web服务进行编程”,而是会针对“客户端接口”进行编程。这样,您就可以轻松地更换东西。
我发现以下规则对我有帮助:
1. 当我们有多种对象类型时,我们使用Java接口。如果只有一个对象,我看不出有什么意义。如果某个想法至少有两个具体实现,则使用Java接口。
2. 如果如上所述,您想将来自外部系统(存储系统)的解耦引入到您自己的系统(本地DB)中,则还需使用接口。
请注意,有两种考虑使用它们的方式。

2

问:......“你能使用任何实现接口的类吗?”
答:- 是的。

问:......“你什么时候需要这样做?”
答:- 每次你需要一个实现接口的类时。

注意:我们不能实例化未被类实现的接口 - 是真的。

  • 为什么?
  • 因为接口只有方法原型,没有定义(只有函数名称,没有逻辑)

AnIntf anInst = new Aclass();
// 只有在Aclass实现AnIntf时才能这样做。
// anInst将具有Aclass引用。


注意:现在我们可以理解如果Bclass和Cclass实现了同一个Dintf会发生什么。

Dintf bInst = new Bclass();  
// now we could call all Dintf functions implemented (defined) in Bclass.

Dintf cInst = new Cclass();  
// now we could call all Dintf functions implemented (defined) in Cclass.

我们拥有的是:相同的接口原型(接口中的函数名称),以及调用不同的实现。

参考资料: 函数原型 - 维基百科


1
“程序接口化”意味着不要直接提供硬编码,也就是说你的代码应该可以扩展而不会破坏以前的功能。只做扩展,不编辑以前的代码。

1

面向接口编程允许无缝更改由接口定义的合同实现。它允许合同和特定实现之间的松耦合。

IInterface classRef = new ObjectWhatever()

您可以使用任何实现IInterface的类?什么时候需要这样做?

看看这个SE问题,有很好的例子。

为什么应该优先选择Java类的接口?

使用接口会影响性能吗?

如果有,会影响多少?

是的。它将在亚秒级别上产生轻微的性能开销。但如果您的应用程序需要动态更改接口的实现,请不要担心性能影响。

如何避免维护两个代码片段而不必进行更改?

如果您的应用程序需要多个接口实现,请不要尝试避免它们。在接口与一个特定实现的紧密耦合缺失的情况下,您可能需要部署补丁以将一种实现更改为另一种实现。

一个很好的用例:策略模式的实现:

策略模式的现实世界示例


0

首先,让我们从一些定义开始:

接口 n. 对象操作定义的所有签名集合称为对象的接口。

类型 n. 特定的接口。

如上所述,一个简单的接口示例是所有PDO对象方法,例如query()commit()close()等,作为一个整体而不是分开。这些方法,即其接口定义了可以发送到对象的完整消息和请求的集合。

如上所述,类型是一个特定的接口。我将使用虚构的形状接口来演示:draw()getArea()getPerimeter()等。

如果一个对象是数据库类型,我们的意思是它接受数据库接口的消息/请求,例如query()commit()等。对象可以有许多类型。只要实现了其接口,你可以将数据库对象设置为形状类型,这就是子类型化

许多对象可以是许多不同的接口/类型,并以不同的方式实现该接口。这使我们能够替换对象,让我们选择要使用哪个对象。也称为多态性。

客户端只会知道接口而不是实现。

因此,本质上编程到一个接口将涉及制作某种抽象类,例如Shape,仅指定接口,即draw()getCoordinates()getArea()等。然后有不同的具体类实现这些接口,例如Circle类,Square类,Triangle类。因此,编程时要针对接口而不是实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接