了解“编程接口”


29

我遇到过很多术语“编程为接口而不是实现”,我想我有点理解它的意思。但是我想确保我了解它的好处以及可能的实现。

“对接口进行编程”意味着,在可能的情况下,应该引用一个类的更抽象级别(一个接口,抽象类,有时甚至是某种超类),而不是引用一个具体的实现。

Java中的一个常见示例是使用:

List myList = new ArrayList();代替ArrayList myList = new ArrayList();

我对此有两个问题:

  1. 我想确保我了解这种方法的主要好处。我认为好处主要是灵活性。将对象声明为更高级的引用,而不是具体的实现,可以在整个开发周期和整个代码中提供更大的灵活性和可维护性。它是否正确?灵活性是主要好处吗?

  2. 还有更多的“编程接口”方法吗?还是“将变量声明为接口而不是具体的实现”是此概念的唯一实现?

不是在谈论Java构造接口。我说的是OO原则:“编程为接口,而不是实现”。按照这个原理,世界“接口”是指一个类的任何“超类型” -接口,抽象类或简单的超类,它比更具体的子类更抽象,更不具体。




1
该答案为您提供了一个易于理解的示例程序员
。stackexchange.com/ a / 314084/61852

Answers:


44

“对接口进行编程”意味着,在可能的情况下,应该引用一个类的更抽象级别(一个接口,抽象类,有时甚至是某种超类),而不是引用一个具体的实现。

这是不正确的。或至少,这并不完全正确。

更重要的一点是从程序设计的角度出发。在这里,“面向接口编程”是指专注于您的设计什么样的代码是干什么的,不是怎么它做它。这是一个至关重要的区别,它将您的设计推向正确性和灵活性。

主要思想是域的更改远比软件更改慢。假设您有可以跟踪购物清单的软件。在80年代,该软件可以在命令行和软盘上的一些平面文件上运行。然后,您获得了一个UI。然后,您可以将列表放入数据库中。稍后可能会转移到云,手机或Facebook集成中。

如果您是专门针对实现(软盘和命令行)设计代码的,则准备更改的准备不足。如果您是围绕界面设计代码的(操作杂货店清单),则可以自由更改实现。


感谢您的回答。从您写的内容来看,我认为我理解“编程接口”的含义以及它的好处。但是我有一个问题-这个概念的最常见的具体例子是:在创建对象的引用时,将引用类型设为该对象实现的接口类型(或该对象继承的超类),而不是将引用类型设为对象类型。(又名,List myList = new ArrayList()而不是ArrayList myList = new ArrayList()。(问题在下一条评论中)
Aviv Cohn 2014年

我的问题是:您能为我提供更多有关“编程接口”原理的实际代码示例吗?除了我在上一条评论中描述的常见示例之外?
阿维夫·科恩

6
NOList/ ArrayList根本不是我在说什么。这更像是提供一个Employee对象,而不是提供用于存储Employee记录的一组链接表。或者提供一个界面来遍历歌曲,而不关心那些歌曲是否经过随机播放,CD上或从Internet流传输。他们只是一首歌。
Telastyn 2014年

您是否会说“对接口编程,而不是对实现编程”是表达Abstraction OO原理的原理?
阿维夫·科恩

1
@AvivCohn我建议使用SpringFramework作为一个很好的例子。可以通过对界面进行自己的设置来增强或定制Spring中的所有内容,并且Springs的主要行为是某个功能将按预期运行...或者根据您的预期进行定制。对接口进行编程也是设计集成框架的最佳策略。Spring再次使用它的spring-integration。在设计时,对接口进行编程是我们使用UML所做的。或者这就是我会做的。从我的工作方式中提取自己,并专注于要做的事情。
2016年

18

我对“接口编程”的理解与问题或其他答案所暗示的是不同的。这并不是说我的理解是正确的,也不是说其他​​答案中的内容不是好主意,只是当我听到这个词时,它们并不是我所想的。

对接口进行编程意味着,当您看到某个编程接口(类库,一组函数,网络协议或其他任何内容)时,您只能使用该接口保证的功能。您可能具有有关底层实现的知识(您可能已经写过),但是永远不要使用该知识。

例如,假设API为您提供了一些不透明的值,这是内部某些内容的“句柄”。您的知识可能会告诉您该句柄实际上是一个指针,并且您可以取消引用它并访问一些值,这可能使您可以轻松地完成想要执行的某些任务。但是界面没有为您提供该选项。这是您对特定实现的了解。

这样做的问题是,它在代码和实现之间建立了牢固的耦合,正是接口应该避免的耦合。根据不同的政策,这可能意味着不再可以更改实现,因为这会破坏您的代码,或者意味着您的代码非常脆弱,并且在基础实现的每次升级或更改时都会中断。

一个典型的例子是为Windows编写的程序。WinAPI是一个接口,但是许多人使用的技巧因Windows 95中的特定实现而起作用。这些技巧可能使它们的程序更快,或者使它们可以用比原先所需要的更少的代码来完成工作。但是这些技巧还意味着该程序将在Windows 2000上崩溃,因为该API的实现方式有所不同。如果该程序足够重要,Microsoft可能实际上会继续执行,并对其实现进行一些修改,以便该程序可以继续工作,但是这样做的代价是Windows代码的复杂性(伴随所有随之而来的问题)增加。对于Wine员工来说,这也使工作变得更加艰辛,因为他们也尝试实现WinAPI,但是他们只能参考文档来了解如何执行WinAPI,


这是一个好点,在某些情况下我经常听到这一点。
Telastyn 2014年

我明白你的意思。因此,让我看看我是否可以使您的发言适应常规编程:假设我有一个使用抽象类B的功能的类(类A)。类C和D继承了类B-它们为所提供的内容提供了具体的实现。据说上课。如果A类直接使用C或D类,则称为“编程实现”,这不是一个非常灵活的解决方案。但是,如果类A使用对类B的引用(以后可以将其设置为C实现或D实现),则它将使事情变得更加灵活和可维护。它是否正确?
阿维夫·科恩

如果这是正确的话,那么比我的问题是-除了“使用接口引用而不是具体类引用”的通用示例之外,是否还有更多具体示例可用于“对接口编程”?
阿维夫·科恩

2
@AvivCohn在回复中有点晚,但是一个具体的例子是万维网。在浏览器大战(IE 4时代)期间,网站的编写不是针对任何规范的内容,而是针对某些浏览器(Netscape或IE)的怪癖。这基本上是针对实现而不是接口进行编程。
塞巴斯蒂安·雷德尔

9

我只能谈谈我的个人经历,因为这也从未被正式教给我。

您的第一点是正确的。获得的灵活性来自于无法意外调用不应调用的具体类的实现细节的原因。

例如,考虑一个ILogger当前作为具体LogToEmailLogger类实现的接口。本LogToEmailLogger类公开所有的ILogger方法和属性,又恰好有一个具体的实施属性sysAdminEmail

在您的应用程序中使用记录器时,设置无需使用任何代码sysAdminEmail。此属性应在记录器安装过程中设置,并且应该对世界隐藏。

如果要针对具体的实现进行编码,那么使用记录器时可能会意外地设置实现属性。现在,您的应用程序代码已与记录器紧密耦合,要切换到另一个记录器,首先需要将您的代码与原始记录器解耦。

从这个意义上讲,对接口进行编码会松耦合

关于您的第二点:我看到的对接口进行编码的另一个原因是降低了代码的复杂性。

例如,假设我有以下接口一场比赛I2DRenderableI3DRenderableIUpdateable。单个游戏组件同时具有2D和3D可渲染内容的情况并不少见。其他组件可能只有2D,其他组件只有3D。

如果2D渲染是由一个模块完成的,则维护一个I2DRenderables 的集合是有意义的。不管它的集合中的对象I3DRenderable还是IUpdateble其他模块都将负责处理对象的那些方面,都没有关系。

将可渲染对象存储为的列表I2DRenderable可降低渲染类的复杂性。3D渲染和更新逻辑与它无关,因此可以并且应该忽略其子对象的那些方面。

从这个意义上讲,对接口进行编码可以通过隔离问题来降低复杂性。


4

这里使用的单词interface可能有两种用法。您在问题中主要指的接口是Java接口。这是一个专门的Java概念,更广泛地说是一个编程语言接口。

我想说,对接口进行编程是一个更广泛的概念。现在可以在许多网站上使用的流行的REST API是在更高级别上对接口进行编程的更广泛概念的另一个示例。通过在代码的内部工作与外部世界(互联网上的人们,其他程序,甚至同一程序的其他部分)之间创建一层,您可以更改代码内的任何内容,只要您不更改外部世界所期望的,这是由您打算兑现的接口或合同定义的。

这样一来,您便可以灵活地重构内部代码,而不必告诉依赖于其接口的所有其他内容。

这也意味着您的代码应该更稳定。通过坚持使用界面,您不应破坏其他人的代码。如果确实需要更改接口,则可以发布API的新主版本(从1.abc到2.xyz),这表明新版本的接口中有重大更改。

正如@Doval在此答案的注释中指出的那样,还存在错误的局部性。我认为这些都归结为封装。就像您将其用于面向对象设计中的对象一样,该概念在更高级别上也很有用。


1
通常忽略的好处是错误的位置。假设您需要一个Map,然后使用二叉树实现它。为了使它起作用,键需要进行一些排序,并且需要保持不变,即“小于”当前节点键的键在左子树上,而“大于”键的键在左子树上。正确的子树。当您将Map的实现隐藏在接口后面时,如果Map查找出错,您就会知道该错误一定在Map模块中。如果已暴露,则该错误可能在程序中的任何位置。对我来说,这是主要的好处。
2014年

4

真实世界的类比可能会有所帮助:

接口中的市电插头。
是; 电视,收音机,吸尘器,洗衣机等电源线末端的那三针东西。

带有主插头(即实现“具有主插头”接口)的任何设备都可以完全相同的方式处理。它们都可以插入墙壁插座,并可以从该插座中汲取能量。

每个单独的设备执行的操作完全不同。您用电视清洁地毯不会走得很远,而且大多数人不会看洗衣机来娱乐。但是,所有这些电器都有可以插入墙壁插座的共同行为。

这就是接口带给您的。可以由许多不同类的对象执行的
统一行为,而无需继承的复杂性。


1

术语“编程到接口”有很多解释。软件开发中的接口是一个非常常见的词。这是我向经过多年培训的初级开发人员解释此概念的方法。

在软件体系结构中,存在许多自然界限。常见的例子包括

  • 客户端和服务器进程之间的网络边界
  • 应用程序和第三方库之间的API边界
  • 程序中不同业务域之间的内部代码边界

重要的是,当这些自然边界存在时,将对其进行识别,并指定该边界的行为方式。您不是通过“另一面”的行为来测试软件,而是根据您的交互是否符合规范来进行测试。

其后果是:

  • 只要外部组件实现规范,就可以交换外部组件
  • 单元测试以验证正确行为的自然点
  • 集成测试变得很重要-规范是否模棱两可?
  • 作为开发人员,您在执行特定任务时所关注的领域较小

尽管其中的大部分可能与类和接口有关,但重要的是要意识到它也与数据模型,网络协议有关,并且以更通用的方式与多个开发人员一起工作。

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.