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

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

如果你要这样做

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.

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

问:——……“你能使用任何实现接口的类吗?” A:是的。 问:——……“你什么时候需要这样做?” 答:-每次你都需要一个实现接口的类。

注意:我们不能实例化一个没有被类实现的接口- True。

为什么? 因为接口只有方法原型,没有定义(只有函数名,没有它们的逻辑)

AnIntf anInst = new class(); //我们可以这样做,只有当类实现AnIntf。 // anInst将有类引用。


注意:现在我们可以理解如果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.

我们拥有:相同的接口原型(接口中的函数名),调用不同的实现。

参考书目: 原型——维基百科

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

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

我决定最好是为每个控件类型创建一个抽象基类,它可以容纳几乎所有的实际逻辑,然后是派生类型,以处理两个组件之间的差异。然而,如果我不得不一直担心类型,基类就不能在这些组件上执行操作(好吧,它们可以,但每个方法中都有一个“if”语句或开关)。

我为这些组件定义了一个简单的接口,所有基类都与该接口对话。现在当我改变一些东西,它几乎“只是工作”在任何地方,我没有代码复制。

“编程到接口”意味着不要直接提供硬代码,这意味着你的代码应该在不破坏之前功能的情况下进行扩展。只是扩展,而不是编辑前面的代码。

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

它的实际意思是:

===

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

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

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

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

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

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

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

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