我曾多次看到有人提到这一点,但我不清楚这是什么意思。你什么时候,为什么要这么做?

我知道接口是做什么的,但我不清楚这一点的事实使我认为我错过了正确使用它们。

如果你要这样做

IInterface classRef = new ObjectWhatever()

你可以使用任何实现IInterface的类吗?你什么时候需要这样做?我能想到的唯一一件事是,如果你有一个方法,你不确定什么对象将被传递,除了它实现IInterface。我不知道你需要多久做一次。

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


当前回答

这里有一些关于这个问题的很好的答案,涉及到各种关于接口和松耦合代码、控制反转等等的细节。有一些相当令人兴奋的讨论,所以我想借此机会把事情分解一下,以理解为什么界面是有用的。

When I first started getting exposed to interfaces, I too was confused about their relevance. I didn't understand why you needed them. If we're using a language like Java or C#, we already have inheritance and I viewed interfaces as a weaker form of inheritance and thought, "why bother?" In a sense I was right, you can think of interfaces as sort of a weak form of inheritance, but beyond that I finally understood their use as a language construct by thinking of them as a means of classifying common traits or behaviors that were exhibited by potentially many non-related classes of objects.

例如,假设你有一款SIM游戏,并拥有以下类:

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

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

显然,这两个对象在直接继承方面没有任何共同之处。但是,你可以说它们都很烦人。

让我们假设我们的游戏需要有一些随机的东西让玩家在吃饭时感到厌烦。这可以是HouseFly或Telemarketer,或两者兼而有之,但你如何在一个功能上兼顾两者呢?你如何要求每个不同类型的对象以同样的方式“做他们讨厌的事情”?

要认识到的关键是,Telemarketer和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的契约——这个契约很简单。你只需要变得烦人。在这方面,我们可以建立以下模型:

class DiningRoom {

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

    void ServeDinner() {
        when diningPeople are eating,

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

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

The ServeDinner method is called when dinner is served and our people in the dining room are supposed to eat. In our little game, that's when our pests do their work -- each pest is instructed to be annoying by way of the IPest interface. In this way, we can easily have both Telemarketers and HouseFlys be annoying in each of their own ways -- we care only that we have something in the DiningRoom object that is a pest, we don't really care what it is and they could have nothing in common with other.

这个非常人为的伪代码示例(比我预期的要长得多)只是为了说明一些事情,这些事情最终为我打开了一盏灯,让我知道什么时候可以使用接口。我为这个例子的愚蠢提前道歉,但希望它有助于您的理解。而且,可以肯定的是,您在这里收到的其他回答确实涵盖了当今在设计模式和开发方法中使用接口的全部范围。

其他回答

我曾经给学生的一个具体例子是他们应该写作

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

而不是

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

在一个短程序中,它们看起来完全相同,但如果在程序中继续使用myList 100次,就会开始看到区别。第一个声明确保只调用myList上由List接口定义的方法(因此没有ArrayList特定的方法)。如果您以这种方式对接口进行了编程,那么稍后您就可以确定您确实需要

List myList = new TreeList();

你只需要在这一点上修改代码。您已经知道,其余的代码不会因为更改实现而被破坏,因为您对接口进行了编程。

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

public ArrayList doSomething(HashMap map);

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

public List doSomething(Map map);

现在,不管您返回什么样的List,或者作为参数传入什么样的Map。在doSomething方法中所做的更改不会强制您更改调用代码。

我坚信困难的问题应该用现实世界中简单的答案来解释。在软件设计领域,这是非常重要的。

看看你家里、学校、教堂的任何一扇门……任何建筑。

想象一下,有些门的底部有危险(所以你必须鞠躬才能与开门或关门的门互动),

或者其他人只能坐在左上角(所以,一些侏儒、残疾人或凯文·哈特不会觉得这种门很有趣,也不会觉得它很好用)。

所以设计是关键,创建程序让其他人可以开发/使用它。

接口的作用是让大型项目中的初级/高级开发人员更容易工作,这样每个人都知道自己在做什么,而不需要别人的帮助,这样你就可以尽可能顺利地工作(理论上)。


[1]:如何?通过揭示价值的形态。所以,你不需要文档,因为代码本身是不言自明的(棒极了)。


这个答案并不是针对特定语言的,而是概念驱动的(毕竟,人类是通过编写代码来创建工具的)。

有很多解释,但让它更简单。以List为例。可以使用as实现一个列表:

内部数组 链表 其他实现

通过构建一个接口,比如一个List。您只编写了List的定义或List在实际中的含义。

你可以在内部使用任何类型的实现比如数组实现。但是假设您希望由于某种原因(比如bug或性能)更改实现。然后你只需要改变声明List<String> ls = new ArrayList<String>()为List<String> ls = new LinkedList<String>()。

在代码的其他地方,你不需要改变任何东西;因为其他所有东西都建立在List的定义之上。

面向接口编程非常棒,它促进了松耦合。正如@lassevk提到的,控制反转是一个很好的应用。

此外,研究SOLID原则。这是一个系列视频

它通过硬编码(强耦合示例),然后查看接口,最后进展到IoC/DI工具(NInject)。

为了补充现有的帖子,当开发人员同时在不同的组件上工作时,有时对接口进行编码有助于大型项目。您所需要做的就是预先定义接口并为它们编写代码,而其他开发人员则为您正在实现的接口编写代码。