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

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

如果你要这样做

IInterface classRef = new ObjectWhatever()

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

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


当前回答

c++的解释。

把接口看作是类的公共方法。

然后你可以创建一个“依赖”于这些公共方法的模板来执行它自己的函数(它在类的公共接口中进行函数调用)。假设这个模板是一个容器,就像Vector类一样,它所依赖的接口是一个搜索算法。

任何定义函数/接口Vector调用的算法类都将满足“契约”(就像有人在原始回复中解释的那样)。算法甚至不需要是相同的基类;唯一的要求是向量所依赖的函数/方法(接口)在你的算法中定义。

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

您可能还想设计其他容器(列表、队列),通过让它们实现搜索算法所依赖的接口/契约,来利用与Vector相同的搜索算法。

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

至于“错过”事情是如何运作的;这是非常重要的(至少在c++中),因为这是大多数标准模板库框架的运作方式。

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

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

其他回答

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

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

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

If you program in Java, JDBC is a good example. JDBC defines a set of interfaces but says nothing about the implementation. Your applications can be written against this set of interfaces. In theory, you pick some JDBC driver and your application would just work. If you discover there's a faster or "better" or cheaper JDBC driver or for whatever reason, you can again in theory re-configure your property file, and without having to make any change in your application, your application would still work.

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

神健壮健壮健壮健壮

除了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

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

单元测试

在过去的两年里,我写了一个业余项目,但我没有为它写单元测试。在写了大约50K行代码后,我发现编写单元测试是非常必要的。 我没有使用接口(或者很少使用)……当我做第一个单元测试时,我发现它很复杂。为什么?

因为我必须创建大量的类实例,用作类变量和/或参数的输入。所以测试看起来更像集成测试(必须制作一个完整的类“框架”,因为所有的类都捆绑在一起)。

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

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

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)的好处。

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

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.

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