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

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

如果你要这样做

IInterface classRef = new ObjectWhatever()

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

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


当前回答

如果我正在编写一个新类Swimmer来添加swim()功能,并且需要使用类的对象说Dog,并且这个Dog类实现了声明swim()的接口Animal。

在层次的顶端(动物),它是非常抽象的,而在底层(狗),它是非常具体的。我认为“面向接口编程”的方式是,当我编写Swimmer类时,我想针对层次结构最高的接口编写代码,在本例中是Animal对象。接口没有实现细节,因此使代码松耦合。

实现细节可以随着时间的推移而改变,但是,它不会影响剩余的代码,因为您所与之交互的是接口而不是实现。你不关心实现是什么样的……您所知道的是,将会有一个实现该接口的类。

其他回答

你应该看看反转控制:

Martin Fowler:控制反转容器和依赖注入模式 维基百科:控制反转

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

IInterface classRef = new ObjectWhatever();

你可以这样写:

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

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

如果我们不考虑IoC,那么您可以编写的代码知道它可以与执行特定操作的对象进行对话,但不知道是哪种类型的对象或它如何执行操作。

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

至于你带圆括号的问题“另外,你如何编写一个方法来接受一个实现接口的对象?”这可能吗?”,在c#中,你可以简单地使用接口类型作为参数类型,如下所示:

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

这直接插入到“与做特定事情的对象对话”中。上面定义的方法知道从对象中期望得到什么,它实现了IInterface中的所有内容,但它并不关心它是哪种类型的对象,只关心它遵守契约,这就是接口。

例如,你可能对计算器很熟悉,也可能在你的生活中使用过不少计算器,但大多数时候它们都是不同的。另一方面,你知道标准计算器应该如何工作,所以你能够使用所有的计算器,即使你不能使用每个计算器都没有的特定功能。

这就是界面的美妙之处。你可以写一段代码,它知道它会得到传递给它的对象,它可以从这些对象中期待特定的行为。它并不关心它是什么类型的对象,只关心它支持所需的行为。

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

我们为windows窗体定制了翻译系统。这个系统循环遍历表单上的控件,并翻译每个控件中的文本。这个系统知道如何处理基本的控件,比如拥有文本属性的控件类型,以及类似的基本东西,但对于任何基本的东西,它都做不到。

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

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

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

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

单元测试

在过去的两年里,我写了一个业余项目,但我没有为它写单元测试。在写了大约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)的好处。

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

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

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

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

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

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


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


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

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

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.

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

面向接口而不是实现的代码与Java无关,也与它的接口构造无关。

这个概念是在模式/四人帮的书中突出的,但很可能在那之前就已经存在了。这个概念在Java出现之前就已经存在了。

Java Interface构造的创建就是为了帮助实现这一想法(以及其他一些事情),人们过于关注作为意义中心的构造,而不是最初的意图。然而,这也是为什么我们在Java、c++、c#等语言中有公共和私有方法和属性的原因。

It means just interact with an object or system's public interface. Don't worry or even anticipate how it does what it does internally. Don't worry about how it is implemented. In object-oriented code, it is why we have public vs. private methods/attributes. We are intended to use the public methods because the private methods are there only for use internally, within the class. They make up the implementation of the class and can be changed as required without changing the public interface. Assume that regarding functionality, a method on a class will perform the same operation with the same expected result every time you call it with the same parameters. It allows the author to change how the class works, its implementation, without breaking how people interact with it.

And you can program to the interface, not the implementation without ever using an Interface construct. You can program to the interface not the implementation in C++, which does not have an Interface construct. You can integrate two massive enterprise systems much more robustly as long as they interact through public interfaces (contracts) rather than calling methods on objects internal to the systems. The interfaces are expected to always react the same expected way given the same input parameters; if implemented to the interface and not the implementation. The concept works in many places.

不要认为Java接口与“面向接口编程,而不是面向实现”的概念有什么关系。它们可以帮助应用概念,但它们不是概念。