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

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

如果你要这样做

IInterface classRef = new ObjectWhatever()

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

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


当前回答

我是这个问题的后来者,但我想在这里提到的是,在GoF (Gang of Four)设计模式一书中,“为接口编程,而不是为实现编程”这一行有一些很好的讨论。

它在第18页说:

针对接口编程,而不是针对实现编程 不要将变量声明为特定具体类的实例。相反,只提交到由抽象类定义的接口。你会发现这是本书设计模式的一个共同主题。

在此之上,它是这样开始的:

仅根据抽象类定义的接口来操作对象有两个好处: 客户端仍然不知道他们使用的对象的具体类型,只要对象遵循客户端期望的接口。 客户端仍然不知道实现这些对象的类。客户端只知道定义接口的抽象类。

So in other words, don't write it your classes so that it has a quack() method for ducks, and then a bark() method for dogs, because they are too specific for a particular implementation of a class (or subclass). Instead, write the method using names that are general enough to be used in the base class, such as giveSound() or move(), so that they can be used for ducks, dogs, or even cars, and then the client of your classes can just say .giveSound() rather than thinking about whether to use quack() or bark() or even determine the type before issuing the correct message to be sent to the object.

其他回答

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

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.

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

我是这个问题的后来者,但我想在这里提到的是,在GoF (Gang of Four)设计模式一书中,“为接口编程,而不是为实现编程”这一行有一些很好的讨论。

它在第18页说:

针对接口编程,而不是针对实现编程 不要将变量声明为特定具体类的实例。相反,只提交到由抽象类定义的接口。你会发现这是本书设计模式的一个共同主题。

在此之上,它是这样开始的:

仅根据抽象类定义的接口来操作对象有两个好处: 客户端仍然不知道他们使用的对象的具体类型,只要对象遵循客户端期望的接口。 客户端仍然不知道实现这些对象的类。客户端只知道定义接口的抽象类。

So in other words, don't write it your classes so that it has a quack() method for ducks, and then a bark() method for dogs, because they are too specific for a particular implementation of a class (or subclass). Instead, write the method using names that are general enough to be used in the base class, such as giveSound() or move(), so that they can be used for ducks, dogs, or even cars, and then the client of your classes can just say .giveSound() rather than thinking about whether to use quack() or bark() or even determine the type before issuing the correct message to be sent to the object.

即使当我们不依赖于抽象时,面向接口编程也是有利的。

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

防止我们做一些不合时宜的事,还有 让我们在将来安全地更改实现。

例如,考虑实现Friend和Employee接口的Person类。

class Person implements AbstractEmployee, AbstractFriend {
}

在这个人的生日的情况下,我们编程到朋友界面,以防止像对待员工一样对待这个人。

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

在这个人的工作环境中,我们对雇员界面进行编程,以防止模糊工作场所的边界。

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

太好了。我们在不同的环境中都有适当的表现,我们的软件运行良好。

在遥远的未来,如果我们的业务改变为与狗打交道,我们可以相当容易地更改软件。首先,我们创建一个实现Friend和Employee的Dog类。然后,我们安全地将新的Person()更改为新的Dog()。即使两个函数都有数千行代码,这个简单的编辑也可以工作,因为我们知道以下是正确的:

Function party只使用Person的Friend子集。 函数workplace只使用Person的Employee子集。 类Dog实现了Friend和Employee接口。

另一方面,如果任何一方或工作场所都针对Person进行编程,就会有同时拥有特定于Person的代码的风险。从“人”改为“狗”需要我们梳理代码,删除任何“狗”不支持的“人”特定代码。

寓意:为接口编程可以帮助我们的代码适当地运行,并为更改做好准备。它还使我们的代码能够依赖于抽象,这带来了更多的好处。

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

一个对象的操作所定义的所有签名的集合称为该对象的接口

输入n.指定接口

上面定义的接口的一个简单例子是所有PDO对象方法,如query()、commit()、close()等,作为一个整体,而不是分开。这些方法,即它的接口定义了可以发送到对象的完整消息和请求集。

上面定义的类型是一个特定的接口。我将使用创建的形状界面来演示:draw(), getArea(), getPerimeter()等。

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

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

客户机将只知道接口,而不知道实现。

因此,从本质上讲,编程到一个接口将涉及到一些类型的抽象类,如只指定接口的Shape,即draw(), getCoordinates(), getArea()等。然后让不同的具体类实现这些接口,比如圆形类,方形类,三角形类。因此,针对接口而不是实现编程。

为一个界面编码是一种哲学,而不是特定的语言结构或设计模式——它指导你为了创建更好的软件系统而遵循正确的步骤顺序(例如,更有弹性,更可测试,更可伸缩,更可扩展,以及其他良好的特性)。

它的实际意思是:

===

在跳到实现和编码(如何)之前,先想想是什么:

你的系统中应该有哪些黑箱, 每个盒子的职责是什么? 每个“客户端”(即其他盒子,第三方“盒子”,甚至人类)应该与它(每个盒子的API)通信的方式是什么?

在你理清上面的问题之后,继续执行这些方框(HOW)。

首先考虑什么是盒子,什么是它的API,这将引导开发人员提炼盒子的职责,并为自己和未来的开发人员标记它的公开细节(“API”)和隐藏细节(“实现细节”)之间的区别,这是一个非常重要的区别。

一个直接且容易注意到的收获是,团队可以在不影响总体架构的情况下更改和改进实现。它还使系统更具可测试性(它与TDD方法配合得很好)。

= = = 除了我上面提到的特质,你还可以在这个方向上节省很多时间。

微服务和DDD,如果做得正确,是“编码到接口”的很好的例子,然而这个概念在每个模式中获胜,从巨石到“无服务器”,从BE到FE,从面向对象到功能性,等等....

我强烈推荐这种方法用于软件工程(我基本上相信它在其他领域也完全有意义)。