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


813

我已经看过几次这个问题了,我不清楚它是什么意思。您何时以及为什么要这样做?

我知道接口的作用,但是我不清楚这一点,这让我觉得我错过了正确使用它们的机会。

如果要这样做,是否只是这样:

IInterface classRef = new ObjectWhatever()

您可以使用任何实现的类IInterface吗?您什么时候需要这样做?我唯一能想到的是,如果您有一个方法,并且不确定要实现的对象将传递什么对象IInterface。我不认为您需要多久这样做一次。

另外,如何编写一个方法来接受实现接口的对象?那可能吗?


3
如果您还记得并且您的程序需要是最佳的,那么在编译之前,您可能希望将Interface声明交换为实际的实现。使用接口时会增加间接级别,从而降低性能。尽管可以将您的编程代码分发给接口...
Ande Turner

18
@安德·特纳(Ande Turner):这是一个糟糕的建议。1)。“您的程序需要优化”不是换出接口的充分理由!然后您说“尽管将编程的代码分发给接口...”,所以您建议给定要求(1),然后释放次优代码?
米奇小麦

74
这里的大多数答案不太正确。它根本不表示甚至暗示“使用interface关键字”。接口是关于如何使用某些内容的规范-与合同同义(查找它)。与之不同的是实施,即合同的履行方式。仅针对方法/类型的保证进行编程,以便在以仍然遵守合同的方式更改方法/类型时,不会破坏使用该方法/类型的代码。
jyoungdev

2
@ apollodude217实际上是整个页面上的最佳答案。至少是标题中的问题,因为这里至少有3个完全不同的问题……
Andrew Spencer

4
诸如此类问题的根本问题在于,它假定“编程到接口”的意思是“将所有内容包装在抽象接口中”,如果您认为该术语早于Java样式抽象接口的概念,那就太愚蠢了。
乔纳森·艾伦

Answers:


1633

对于这些问题,这里有一些奇妙的答案,涉及关于接口和松耦合代码,控制反转等的各种详细信息。这里有一些相当令人头疼的讨论,所以我想借此机会将事情分解一下,以了解界面为什么有用。

当我刚开始接触接口时,我也对它们的相关性感到困惑。我不明白你为什么需要他们。如果我们正在使用Java或C#之类的语言,那么我们已经拥有继承,我将接口视为一种较弱的继承形式,并认为“为什么要打扰?” 从某种意义上说我是对的,您可以将接口视为一种较弱的继承形式,但除此之外,我最终通过将它们视为对常见的特征或行为进行分类的一种手段,最终将它们用作语言构造。可能有许多不相关的对象类别。

例如,假设您有一个SIM卡游戏,并且具有以下课程:

class HouseFly inherits Insect {
    void FlyAroundYourHead(){}
    void LandOnThings(){}
}

class Telemarketer inherits Person {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}
}

显然,就直接继承而言,这两个对象没有共同之处。但是,您可以说它们都很烦人。

假设我们的游戏需要某种随机的东西,使玩家在吃晚餐时会感到烦恼。这可能是一个HouseFly或一个Telemarketer或两个-但你怎么允许对于用一个单一的功能?以及您如何要求每种不同类型的对象以相同的方式“做烦人的事情”?

要实现的关键是,尽管a Telemarketer和a HouseFly共享一个共同的松散解释的行为,即使它们在建模方面也不相同。因此,让我们创建一个可以实现的接口:

interface IPest {
    void BeAnnoying();
}

class HouseFly inherits Insect implements IPest {
    void FlyAroundYourHead(){}
    void LandOnThings(){}

    void BeAnnoying() {
        FlyAroundYourHead();
        LandOnThings();
    }
}

class Telemarketer inherits Person implements IPest {
    void CallDuringDinner(){}
    void ContinueTalkingWhenYouSayNo(){}

    void BeAnnoying() {
        CallDuringDinner();
        ContinueTalkingWhenYouSayNo();
    }
}

现在,我们有两个类,每个类都可能以自己的方式令人讨厌。而且他们不需要从相同的基类派生出来并共享共同的固有特性-他们只需要满足合同IPest-合同就是简单的。你只需要BeAnnoying。在这方面,我们可以建立以下模型:

class DiningRoom {

    DiningRoom(Person[] diningPeople, IPest[] pests) { ... }

    void ServeDinner() {
        when diningPeople are eating,

        foreach pest in pests
        pest.BeAnnoying();
    }
}

在这里,我们有一间餐厅,可以容纳许多食客和许多害虫-请注意该界面的使用。这意味着在我们的小世界中,pests数组的成员实际上可以是一个Telemarketer对象或一个HouseFly对象。

ServeDinner方法在晚餐时供我们使用,而我们在饭厅里的人应该就餐。在我们的小游戏中,这是我们的害虫执行其工作的时候-指示每种害虫通过IPest界面来烦人。这样,我们可以轻松地同时拥有这两种方式,Telemarketers并且HouseFlys以各自的方式烦人-我们只关心我们在DiningRoom虫体内有某种东西,我们不在乎它到底是什么,它们可能什么也没有与其他共同。

这个非常人为的伪代码示例(比我预期的拖了更长的时间)仅是为了说明最终使我了解何时使用接口的那种事情。对于该示例的愚蠢行为,我事先表示歉意,但希望它对您的理解有所帮助。而且,可以肯定的是,您在此处收到的其他已发布答案确实涵盖了当今在设计模式和开发方法中使用接口的范围。


3
需要考虑的另一件事是,在某些情况下,为“可能”令人烦恼的事物提供接口,并且将各种对象实现BeAnnoying为无操作对象可能会有用。这个接口可能存在的地方,或除,对于事情是烦人的界面(如果存在两个接口时,“事情恼人的”接口应该从“东西,继承可能是恼人的”接口)。使用此类接口的缺点是,实现“烦人”数量的存根方法可能会使实现负担。优点是...
超级猫2012年

4
这些方法无意代表抽象方法-它们的实现与关注接口的问题无关。
彼得·迈耶

33
封装行为(例如IPest)被称为策略模式,以防万一有人有兴趣跟进有关该主题的更多材料的情况……
nckbrz 2014年

9
有趣的是,您没有指出,由于中的对象IPest[]是IPest引用,因此可以调用BeAnnoying()它们,因为它们具有该方法,而如果没有强制转换就无法调用其他方法。但是,BeAnnoying()将为每个对象调用单独的方法。
D. Ben Knoble

4
很好的解释...我只需要在这里说:我从未听说过接口是某种松散的继承机制,但是相反,我知道继承被用作定义接口的不良机制(例如,在常规Python中,一直这样做)。
卡洛斯·H·罗马诺2015年

282

我过去给学生的具体例子是他们应该写

List myList = new ArrayList(); // programming to the List interface

代替

ArrayList myList = new ArrayList(); // this is bad

在一个简短的程序中,它们看起来完全相同,但是如果您继续在程序中使用myList100次,则可能会发现差异。第一个声明确保您只调用接口myList定义的方法List(因此没有ArrayList特定的方法)。如果您已经通过这种方式对接口进行了编程,则以后可以确定您确实需要

List myList = new TreeList();

而您只需要在那一处更改代码。您已经知道,由于对接口进行了编程,因此其余代码不会执行任何更改更改实现操作

当您谈论方法参数和返回值时,好处更加明显(我认为)。以这个为例:

public ArrayList doSomething(HashMap map);

该方法声明将您绑定到两个具体的实现(ArrayListHashMap)。一旦从其他代码中调用了该方法,对这些类型的任何更改都可能意味着您也将不得不更改调用代码。最好对接口进行编程。

public List doSomething(Map map);

现在,List返回哪种类型或Map作为参数传递哪种类型都没有关系。您在doSomething方法中进行的更改不会强制您更改调用代码。


评论不作进一步讨论;此对话已转移至聊天
伊薇特

非常清楚的解释。很有帮助
塞缪尔·卢斯瓦塔(Samuel luswata)

我对您提到的原因有一个疑问:“第一个声明可确保您仅在myList上调用由List接口定义的方法(因此,没有ArrayList特定的方法)。如果您已通过这种方式编程为接口,则稍后可以确定您确实需要List myList = new TreeList();并且只需要在那个位置更改代码即可。” 也许我误会了,我想知道为什么要“确保仅在myList上调用方法”,为什么需要将ArrayList更改为TreeList?
user3014901

1
@ user3014901出于多种原因,您可能想要更改所使用列表的类型。例如,可能具有更好的查找性能。关键是,如果您对List接口进行编程,那么以后可以更轻松地将代码更改为其他实现。
比尔蜥蜴

73

对接口进行编程时说:“我需要此功能,并且我不在乎它来自何处。”

考虑(在Java中)List接口与ArrayListLinkedList具体类的比较。如果我关心的只是我有一个包含多个数据项的数据结构,这些数据项我应该通过迭代访问,那么我会选择一个List(那是99%的时间)。如果我知道需要从列表的任一端进行固定时间的插入/删除,则可以选择LinkedList具体的实现(或更可能使用Queue接口)。如果我知道我需要按索引进行随机访问,则选择ArrayList具体的类。


1
完全同意,即完成与否之间的独立性。通过将系统划分为独立的组件,您最终得到了一个简单且可重用的系统(请参阅创建Clojure的人
撰写的

38

除了消除类之间不必要的耦合之外,使用接口是使代码易于测试的关键因素。通过创建定义您的类上的操作的接口,您可以允许想要使用该功能的类使用它,而无需直接依赖于您的实现类。如果以后您决定更改并使用其他实现,则只需更改实例化实现的代码部分。其余代码无需更改,因为它取决于接口,而不是实现类。

这在创建单元测试中非常有用。在被测类中,您需要它依赖于接口,并通过构造函数或属性设置器将接口的实例注入到该类(或允许其根据需要构建接口实例的工厂)中。该类在其方法中使用提供的(或创建的)接口。当您编写测试时,可以模拟或伪造该接口,并提供一个接口以对单元测试中配置的数据进行响应。您可以执行此操作,因为被测类仅处理接口,而不处理具体实现。任何实现该接口的类(包括您的模拟或假类)都可以。

编辑:以下是文章链接,其中Erich Gamma讨论了他的名言:“接口程序,而不是实现程序。”

http://www.artima.com/lejava/articles/designprinciples.html


3
请再次阅读本访谈:Gamma当然是在谈论OO接口的概念,而不是JAVA或C#特殊类(ISomething)。问题是,尽管大多数人都在谈论关键字,但是我们现在有很多未使用的接口(ISomething)。
西尔文·罗德里格 Sylvain Rodrigue)

很好的采访。请注意以后的读者,采访共有四页。在看到它之前,我几乎会关闭浏览器。
Ad Infinitum'4

37

对接口进行编程绝对与抽象接口无关,就像我们在Java或.NET中看到的那样。它甚至不是OOP概念。

这意味着不要搞乱对象或数据结构的内部。使用抽象程序接口或API与您的数据进行交互。在Java或C#中,这意味着使用公共属性和方法而不是原始字段访问。对于C,这意味着使用函数而不是原始指针。

编辑:对于数据库,这意味着使用视图和存储过程,而不是直接表访问。


5
最佳答案。伽玛在这里给出了类似的解释:artima.com/lejava/articles/designprinciples.html(请参阅第2页)。他指的是OO概念,但您说的对:它比那还大。
西尔文·罗德里格

36

您应该研究控制反转:

在这种情况下,您不会这样写:

IInterface classRef = new ObjectWhatever();

您将编写如下内容:

IInterface classRef = container.Resolve<IInterface>();

这将进入基于规则的设置中 container对象中,并为您构造实际对象,该对象可以是ObjectWhatever。重要的是,您可以用完全使用另一种类型的对象的东西替换此规则,并且您的代码仍然可以工作。

如果我们将IoC放在桌子旁,则您可以编写代码,使它知道可以与执行特定操作的对象通信,但不能与哪种类型的对象或其操作方式进行通信。

传递参数时,这将派上用场。

至于带括号的问题“此外,您如何编写一个方法来接收实现接口的对象?这可能吗?”,在C#中,您只需将接口类型用作参数类型,就像这样:

public void DoSomethingToAnObject(IInterface whatever) { ... }

这将直接插入“与执行特定操作的对象的对话”。上面定义的方法知道该对象有什么用,它实现了IInterface中的所有内容,但是它并不关心它是哪种类型的对象,只关心它遵守协定(即接口是什么)。

例如,您可能对计算器很熟悉,并且在您的日子里可能使用了很多时间,但是大多数时候它们都是不同的。另一方面,您知道标准计算器的工作方式,因此即使您无法使用每个计算器都没有的特定功能,也可以全部使用它们。

这就是界面的美。您可以编写一段代码,它知道它将获得传递给它的对象,可以期望某些行为。它不在乎它是什么对象,只是它支持所需的行为。

让我给你一个具体的例子。

我们有一个针对Windows窗体的定制翻译系统。该系统在窗体上的控件之间循环并在每个控件中翻译文本。系统知道如何处理基本控件,例如具有文本属性的控件类型,以及类似的基本内容,但是对于任何基本内容,它都不够用。

现在,由于控件从我们无法控制的预定义类继承而来,因此我们可以做以下三件事之一:

  1. 建立对我们的翻译系统的支持,以明确检测它正在使用哪种类型的控件,并翻译正确的位(维护噩梦)
  2. 将支持构建到基类中(不可能,因为所有控件都继承自不同的预定义类)
  3. 添加界面支持

所以我们没有。3.我们所有的控件都实现ILocalizable,这是一个接口,为我们提供了一种方法,可以将“自身”转换为翻译文本/规则容器。这样,该窗体不需要知道它找到了哪种控件,只需要知道它实现了特定的接口,并且知道可以调用该方法来本地化该控件。


31
为什么一开始就提到IoC,因为这只会增加更多的混乱。
凯文·勒

1
同意,我会说针对接口进行编程只是使IoC更加容易和可靠的一种技术。
08年

28

接口代码不是实现与Java无关,也不与接口结构无关。

这个概念在《 Patterns / Gang of Four》一书中引起了人们的注意,但很可能早在此之前。这个概念早在Java出现之前就已经存在。

创建Java Interface构造是为了帮助这一想法(除其他外),并且人们已经过于注重作为意义中心而不是原始意图的构造。但是,这就是我们在Java,C ++,C#等中具有公共和私有方法以及属性的原因。

这意味着仅与对象或系统的公共接口进行交互。不必担心,甚至不用担心它在内部如何工作。不用担心它是如何实现的。在面向对象的代码中,这就是我们拥有公共方法与私有方法/属性的原因。我们打算使用公共方法,因为私有方法仅在类内部供内部使用。它们构成了该类的实现,可以在不更改公共接口的情况下根据需要进行更改。假设关于功能,每次使用相同参数调用类时,类上的方法将以相同的预期结果执行相同的操作。它允许作者更改类的工作方式及其实现,而不会破坏人们与之交互的方式。

而且,即使不使用Interface构造,也可以对接口进行编程,而不是对实现进行编程。您可以对接口进行编程,而不是对C ++中没有接口构造的实现进行编程。只要两个大型企业系统通过公共接口(合同)交互而不是在系统内部的对象上调用方法,您就可以更强大地集成它们。在给定相同输入参数的情况下,期望接口始终以相同的预期方式做出反应;如果实现到接口而不是实现。这个概念在许多地方都有效。

认为Java接口与“接口程序,而不是实现”的概念有关。它们可以帮助应用概念,但不是概念。


1
第一句话说明了一切。这应该是公认的答案。
madumlao

14

听起来您似乎了解接口的工作原理,但是不确定何时使用它们以及它们提供了哪些优势。以下是一些使接口有意义的示例:

// if I want to add search capabilities to my application and support multiple search
// engines such as Google, Yahoo, Live, etc.

interface ISearchProvider
{
    string Search(string keywords);
}

然后我可以创建GoogleSearchProvider,YahooSearchProvider,LiveSearchProvider等。

// if I want to support multiple downloads using different protocols
// HTTP, HTTPS, FTP, FTPS, etc.
interface IUrlDownload
{
    void Download(string url)
}

// how about an image loader for different kinds of images JPG, GIF, PNG, etc.
interface IImageLoader
{
    Bitmap LoadImage(string filename)
}

然后创建JpegImageLoader,GifImageLoader,PngImageLoader等。

大多数加载项和插件系统都在接口上工作。

另一个流行的用途是用于存储库模式。假设我要加载来自不同来源的邮政编码列表

interface IZipCodeRepository
{
    IList<ZipCode> GetZipCodes(string state);
}

那么我可以创建XMLZipCodeRepository,SQLZipCodeRepository,CSVZipCodeRepository等。对于我的Web应用程序,我经常在早期创建XML存储库,以便可以在SQL数据库准备就绪之前启动并运行某些东西。数据库准备就绪后,我将编写一个SQLRepository来替换XML版本。我的其余代码保持不变,因为它仅在接口之外运行。

方法可以接受如下接口:

PrintZipCodes(IZipCodeRepository zipCodeRepository, string state)
{
    foreach (ZipCode zipCode in zipCodeRepository.GetZipCodes(state))
    {
        Console.WriteLine(zipCode.ToString());
    }
}

10

当您拥有一组类似的类时,它使您的代码更具可扩展性,并且更易于维护。我是一名初级程序员,所以我不是专家,但是我刚刚完成了一个需要类似内容的项目。

我从事与运行医疗设备的服务器通信的客户端软件的工作。我们正在开发此设备的新版本,其中包含一些客户有时必须配置的新组件。有两种新组件,它们是不同的,但它们也非常相似。基本上,我必须创建两个配置表单,两个列表类,所有内容中的两个。

我认为最好为每个控件类型创建一个抽象基类,以容纳几乎所有的真实逻辑,然后再派生类型以照顾这两个组件之间的差异。但是,如果我不得不一直担心类型的话,基类将无法对这些组件执行操作(当然,它们可以,但是在每个方法中都会有一个“ if”语句或开关) 。

我为这些组件定义了一个简单的接口,并且所有基类都与此接口通信。现在,当我更改某些内容时,它几乎“随处可见”,而且我没有代码重复。


9

那里有很多解释,但要使其更加简单。以一个为例List。可以使用以下方式实现列表:

  1. 内部数组
  2. 链表
  3. 其他实施

通过构建接口,说一个List。您仅编码列表的定义或实际List含义。

您可以在内部使用任何类型的实现,即array实现。但是假设您出于某种原因(例如错误或性能)而希望更改实现。然后,您只需将声明更改List<String> ls = new ArrayList<String>()List<String> ls = new LinkedList<String>()

代码中没有其他地方,您是否需要更改其他任何内容?因为其他所有内容都基于的定义List


8

如果您使用Java编程,则JDBC是一个很好的例子。JDBC定义了一组接口,但是没有说明实现。您的应用程序可以针对这组接口编写。从理论上讲,您选择了一些JDBC驱动程序,您的应用程序将正常运行。如果您发现有一个更快或更便宜的JDBC驱动程序,或者出于某种原因,您可以在理论上再次重新配置属性文件,而不必在应用程序中进行任何更改,那么您的应用程序仍然可以运行。


它不仅可以使用更好的驱动程序,还可以完全更改数据库供应商。
刘坚

3
JDBC非常糟糕,需要替换。寻找另一个例子。
约书亚

JDBC不好,但由于某种原因与接口,实现或抽象级别无关。因此,为了说明问题的概念,它是完美的。
Erwin Smout,

8

接口编程非常棒,它促进了松散耦合。正如@lassevk提到的,控制反转是对此的一种很好的使用。

另外,查看SOLID主体这是一个视频系列

它经过硬编码(严格耦合的示例),然后查看接口,最后进入IoC / DI工具(NInject)


7

我是这个问题的迟来者,但是我想在这里提及“编程到接口,而不是实现”这一行在GoF(四人帮)设计模式一书中进行了很好的讨论。

它在页码上说。18:

编程到接口,而不是实现

不要将变量声明为特定具体类的实例。而是仅提交给抽象类定义的接口。您会发现这是本书设计模式的共同主题。

除此之外,它开始于:

仅根据抽象类定义的接口来操作对象有两个好处:

  1. 只要对象遵守客户期望的接口,客户就不会意识到他们使用的特定类型的对象。
  2. 客户端不知道实现这些对象的类。客户只知道定义接口的抽象类。

因此,换句话说,不要为您的类编写它,这样它就具有quack()用于鸭子的方法,然后具有bark()用于狗的方法,因为它们对于类(或子类)的特定实现太具体了。而是使用足以在基类中使用的通用名称来编写方法,例如giveSound()move(),以便它们可用于鸭子,狗甚至汽车,然后类的客户可以说.giveSound()而不是在发出要发送给对象的正确消息之前,先考虑是使用quack()还是bark()确定类型。


6

除了已经选择的答案(以及此处提供的各种信息)之外,我强烈建议您阅读Head Head Design Patterns的副本。这是一本非常容易阅读的书,将直接回答您的问题,解释其重要性,并向您展示许多可用于利用该原理(以及其他原理)的编程模式。


5

要添加到现有帖子中,当开发人员同时处理单独的组件时,有时对接口进行编码有助于大型项目。您需要做的就是预先定义接口并向其中编写代码,而其他开发人员则向要实现的接口编写代码。


4

这对单元测试也有好处,您可以将自己的类(满足接口的要求)注入到依赖于它的类中


4

即使不依赖抽象,也可以对接口进行编程。

接口的编程迫使我们使用上下文相关的对象子集。这很有帮助,因为它:

  1. 阻止我们做上下文无关的事情,并且
  2. 让我们将来可以安全地更改实施方式。

例如,考虑Person实现FriendEmployee接口的类。

class Person implements AbstractEmployee, AbstractFriend {
}

在该人的生日的上下文中,我们对Friend接口进行编程,以防止将其视为Employee

function party() {
    const friend: Friend = new Person("Kathryn");
    friend.HaveFun();
}

在个人工作中,我们对Employee界面进行编程,以防止工作场所界限模糊。

function workplace() {
    const employee: Employee = new Person("Kathryn");
    employee.DoWork();
}

大。我们在不同的情况下表现良好,我们的软件运行良好。

在不久的将来,如果我们的业务改变为与狗一起工作,我们可以相当轻松地更改软件。首先,我们创建一个Dog同时实现Friend和的类Employee。然后,我们安全地进行更改new Person()new Dog()。即使两个函数都有数千行代码,该简单的编辑也将起作用,因为我们知道以下事实是正确的:

  1. 函数party仅使用Friend子集Person
  2. 函数workplace仅使用Employee子集Person
  3. Dog同时实现FriendEmployee接口。

另一方面,如果partyworkplace要针对进行编程Person,则都有可能都具有Person特定的代码。从更改Person为则Dog需要我们梳理代码以消除Person特定于Dog不支持的。

道德:对接口进行编程有助于我们的代码正确运行并为更改做好准备。它还使我们的代码准备依赖抽象,这带来了更多优势。


1
就是说,假设您没有过于宽泛的界面。
Casey's

4

如果我写了一类新Swimmer添加的功能swim(),需要使用类发言权的一个对象Dog,而这个Dog类实现的接口Animal,它声明swim()

在层次结构的顶部(Animal),非常抽象,而在底部(Dog),则非常具体。我对“接口编程”的思考方式是,在编写Swimmer类时,我想针对与该层次结构(在本例中为Animal对象)最远的接口编写代码。接口没有实现细节,因此使您的代码松耦合。

实现的详细信息可以随时间更改,但是,它不会影响其余的代码,因为您与之交互的只是接口而不是实现。您无需关心实现的方式...您所知道的是,将有一个实现该接口的类。


3

因此,只是为了正确使用接口,我的优点是我可以将方法的调用与任何特定的类分开。而是创建接口的实例,从我选择的实现该接口的任何类中给出实现。因此,允许我拥有许多类,这些类具有相似但略有不同的功能,并且在某些情况下(与接口的意图有关的情况)并不关心它是哪个对象。

例如,我可以有一个运动界面。可以传递使某物“移动”并且实现移动接口的任何对象(人,车,猫)的方法,并告诉其移动。没有该方法的每个人都知道它的类类型。


3

假设您有一个名为“ Zebra”的产品,可以通过插件进行扩展。它通过在某些目录中搜索DLL来找到插件。它加载所有这些DLL,并使用反射来查找实现的所有类。IZebraPlugin然后调用该接口的方法与插件进行通信。

这使得它完全独立于任何特定的插件类-不在乎这些类是什么。它只关心它们满足接口规范。

接口是定义可扩展性点的一种方式。与接口通信的代码更松散地耦合在一起-实际上,它根本不与任何其他特定代码耦合。它可以与多年以后从未见过原始开发人员的人编写的插件进行互操作。

您可以改为使用具有虚函数的基类-所有插件都将从基类派生。但这是更多的限制,因为一个类只能有一个基类,而它可以实现任何数量的接口。


3

C ++解释。

将接口视为类的公共方法。

然后,您可以创建一个“依赖”这些公共方法的模板,以执行其自己的功能(它使在类的公共接口中定义的函数调用)。可以说,此模板是一个容器,例如Vector类,并且它依赖的接口是搜索算法。

定义函数/接口Vector进行调用的任何算法类都将满足“合同”(如原始答复中所解释的)。这些算法甚至不必是相同的基类。唯一的要求是在算法中定义Vector依赖的函数/方法(接口)。

所有这些的目的是,您可以提供任何不同的搜索算法/类,只要它提供了Vector依赖的接口(气泡搜索,顺序搜索,快速搜索)即可。

您可能还希望设计其他容器(列表,队列),这些容器可以通过使用与Vector相同的搜索算法来使它们满足您的搜索算法所依赖的接口/合同。

这样可以节省时间(OOP原则“代码重用”),因为您可以针对创建的每个新对象编写一次而不是一次又一次地编写算法,而不会因继承树过多而使问题复杂化。

至于“错过”事情如何运作;大型应用程序(至少在C ++中),因为这是大多数标准TEMPLATE库框架的工作方式。

当然,当使用继承和抽象类时,对接口进行编程的方法会发生变化。但是原理是一样的,您的公共函数/方法是您的类接口。

这是一个巨大的主题,也是“设计模式”的基石原则之一。


3

在Java中,这些具体类均实现CharSequence接口:

CharBuffer,String,StringBuffer,StringBuilder

这些具体的类除了Object之外没有公共的父类,因此没有任何关联它们的事实,除了它们各自与表示或操纵这样的字符数组有关。例如,一旦实例化String对象,就不能更改String的字符,而可以编辑StringBuffer或StringBuilder的字符。

这些类中的每一个都能够适当地实现CharSequence接口方法:

char charAt(int index)
int length()
CharSequence subSequence(int start, int end)
String toString()

在某些情况下,曾经接受String的Java类库类已被修改为现在接受CharSequence接口。因此,如果您有StringBuilder的实例,则无需提取String对象(这意味着实例化一个新的对象实例),而可以在实现CharSequence接口时直接传递StringBuilder本身。

对于可以将字符附加到基础具体类对象实例的实例的任何情况,某些类实现的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必须检查每个字符。
2014年

3

短篇小说:要求邮递员一个个回家,然后收到包含其上写明地址的封套(信件,文件,支票,礼品卡,申请书,情书)。

假设没有掩护,请邮递员回家后收拾所有东西并交付给其他人,邮递员会感到困惑。

因此,最好将它包裹起来(在我们的故事中,它是界面),然后他会做得很好。

现在,邮递员的工作是仅接收和交付封面(他不会打扰封面中的内容)。

创建一种 interface非实际类型的类型,但以实际类型实现它。

创建接口意味着您的组件可以轻松适应其余代码

我举一个例子。

您具有如下的AirPlane界面。

interface Airplane{
    parkPlane();
    servicePlane();
}

假设您的飞机控制器类中有一些方法,例如

parkPlane(Airplane plane)

servicePlane(Airplane plane)

在您的程序中实现。它不会破坏您的代码。我的意思是,只要接受以下参数,就无需更改AirPlane

因为它会接受任何飞机,尽管实际的类型,flyerhighflyrfighter,等。

另外,在集合中:

List<Airplane> plane; //将搭乘您的所有飞机。

以下示例将清除您的理解。


你有一架实施它的战斗机,所以

public class Fighter implements Airplane {

    public void  parkPlane(){
        // Specific implementations for fighter plane to park
    }
    public void  servicePlane(){
        // Specific implementatoins for fighter plane to service.
    }
}

HighFlyer和其他事件也是如此:

public class HighFlyer implements Airplane {

    public void  parkPlane(){
        // Specific implementations for HighFlyer plane to park
    }

    public void  servicePlane(){
        // specific implementatoins for HighFlyer plane to service.
    }
}

现在想想您的控制器类使用AirPlane了几次,

假设您的Controller类是ControlPlane,如下所示,

public Class ControlPlane{ 
 AirPlane plane;
 // so much method with AirPlane reference are used here...
}

这里的神奇之处在于您可以使您的新AirPlane类型实例尽可能多,而您无需更改ControlPlane类代码。

您可以添加一个实例...

JumboJetPlane // implementing AirPlane interface.
AirBus        // implementing AirPlane interface.

您也可以删除以前创建的类型的实例。


2

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

如果您的A类已经在扩展其他B类,但是您希望该A类也遵循某些准则或实施某些合同,则可以通过“编程接口”策略来实现。


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

接口的程序允许无缝更改接口定义的合同的实现。它允许合同与特定实现之间的松散耦合。

IInterface classRef = new ObjectWhatever()

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

看看这个SE问题以作为一个很好的例子。

为什么应该首选Java类的接口?

使用接口会影响性能吗?

如果是多少?

是。在几秒钟内,它将有轻微的性能开销。但是,如果您的应用程序要求动态更改接口的实现,则不必担心性能影响。

您如何在不必维护两位代码的情况下避免这种情况?

如果您的应用程序需要接口的多种实现,请不要尝试避免它们。如果没有将接口与一种特定实现紧密结合,则可能必须部署补丁程序才能将一种实现更改为另一种实现。

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

战略模式的真实例子


1

程序到接口是GOF书中的术语。我不会直接说它与Java接口有关,而是与实际接口有关。为了实现干净的层分离,您需要在系统之间创建一些分离,例如:假设您有一个要使用的具体数据库,则永远不要“编程到数据库”,而要“编程到存储接口”。同样,您永远不会“编程到Web服务”,而只会编程到“客户端接口”。这样一来,您可以轻松地将内容交换出去。

我发现这些规则对我有帮助:

1。当我们有多种类型的对象时,我们使用Java接口。如果我只有一个对象,我看不到重点。如果至少有两个想法的具体实现,那么我将使用一个Java接口。

2。如果如上所述,您想将外部系统(存储系统)的解耦引入自己的系统(本地DB),那么还可以使用接口。

注意何时有两种方法可以考虑使用它们。希望这可以帮助。


0

另外,我在这里看到了很多很好的解释性答案,所以我想在这里提出自己的观点,包括一些使用此方法时注意到的额外信息。

单元测试

在过去的两年中,我编写了一个爱好项目,但没有编写单元测试。写了大约5万行之后,我发现写单元测试确实很有必要。我没有使用接口(或者非常少用)……当我进行第一次单元测试时,我发现它很复杂。为什么?

因为我必须制作很多类实例,所以它们用作类变量和/或参数的输入。因此,这些测试看起来更像是集成测试(由于所有类都捆绑在一起,因此必须构成一个完整的类的“框架”)。

对接口的恐惧 因此,我决定使用接口。我担心我必须在所有地方(在所有使用的类中)多次实现所有功能。在某种程度上,这是正确的,但是,通过使用继承,可以大大减少继承。

接口和继承 的组合我发现组合非常好使用。我举一个非常简单的例子。

public interface IPricable
{
    int Price { get; }
}

public interface ICar : IPricable

public abstract class Article
{
    public int Price { get { return ... } }
}

public class Car : Article, ICar
{
    // Price does not need to be defined here
}

这种方式不需要复制代码,同时仍然具有使用汽车作为接口(ICar)的优势。


0

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

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

输入 n。特定的界面

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

上面定义的类型是特定的接口。我将使用虚构的形状接口来证明:draw()getArea()getPerimeter()等。

如果对象是数据库类型的,我们的意思是,它接受邮件/数据库接口的请求,query()commit()等对象可以是多种类型的。只要数据库对象实现其接口,就可以使其具有形状类型,在这种情况下,这将是子类型

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

客户端只会知道接口,而不会意识到实现。

因此,从本质上讲,对接口进行编程将涉及制作某种类型的抽象类,例如Shape仅使用指定的接口draw(),例如getCoordinates()getArea()等。然后使不同的具体类实现这些接口,例如Circle类,Square类,Triangle类。因此,编程到接口而不是实现。


0

“程序到接口”意味着没有以正确的方式提供硬代码,这意味着应在不破坏先前功能的情况下扩展代码。只是扩展,而不是编辑先前的代码。

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.