面向对象编程中“接口”的定义是什么


108

好的,我的一个朋友来回探讨编程中“接口”的含义。

什么是“接口”的最佳描述。

对我来说,接口是类的蓝图,这是最好的定义吗?


1
不是欺骗,但它揭示了这个问题,大量的光:stackoverflow.com/questions/444245/...
rmeador

我会说这是一个通用词,但对我来说仍然意味着相同的意思。就像特定的或更抽象的实体的墙一样,它向外部提供一些输入点,并在不了解内部操作的情况下为它们提供输出。我认为可以将其定义为OOP中从低到高的类的抽象。
ikbal

Answers:


179

接口是开发中比较重载和令人困惑的术语之一。

它实际上是抽象和封装的概念。对于给定的“盒子”,它声明该盒子的“输入”和“输出”。在软件领域,这通常意味着可以在框上调用的操作(以及参数),在某些情况下,还指这些操作的返回类型。

尽管通常(在非常好的实践中)在声明附近(例如,通过注释)记录它们,或者选择良好的命名约定,但这并不能定义这些操作的语义。但是,不能保证将遵循这些意图。

打个比方:关掉电视看一下。它的界面是它具有的按钮,各种插头和屏幕。它的语义和行为是接受输入(例如,电缆编程)并具有输出(在屏幕上显示,声音等)。但是,当您查看未插入的电视时,您正在将期​​望的语义投影到界面中。就您所知,插入电视可能会爆炸。但是,根据其“界面”,您可以假定电视不会煮咖啡,因为它没有水。

在面向对象的编程中,接口通常定义具有该接口的类的实例可以响应的方法(或消息)集。

造成混淆的是,在某些语言(如Java)中,存在一个具有特定于语言的语义的实际接口。例如,在Java中,它是一组方法声明,没有实现,但是接口也对应于一种类型,并遵守各种键入规则。

在其他语言中,例如C ++,您没有接口。类本身定义了方法,但是您可以将类的接口视为非私有方法的声明。由于C ++的编译方式,您将获得头文件,而无需实际实现即可获得类的“接口”。您也可以使用带有纯虚函数等的抽象类来模仿Java接口。

接口绝对不是类的蓝图。根据一个定义,蓝图是“详细的行动计划”。接口对动作没有任何承诺!造成混淆的原因是,在大多数语言中,如果您有定义一组方法的接口类型,则实现该方法的类将“重复”相同的方法(但提供了定义),因此该接口看起来像骨架或课程大纲。


我正在阅读有关Objective-C的书,在我看来作者似乎可以互换地使用术语“协议”和“接口”。说“协议”和“接口”是同一件事还是我遗漏了一些东西是正确的吗?
Fazzolini

1
我不会在“接口”中使用“协议”一词。“协议”一词表示有关如何执行动作的详细程度。“接口”确实是一个出色的单词,具有纯英语定义,严格描述了它的确切含义。
OCDev

在Windows中,编程接口的使用非常广泛,因此即使在C ++中,您也可以通过与实现分离的对象的方式来满足“接口”
维多利亚

166

请考虑以下情况:

当僵尸突然袭击您时,您位于一个空旷的大房间中间。

你没有武器。

幸运的是,一个活着的人类正站在房间的门口。

“快!” 你对他大喊。“给我扔东西,我可以用它来打僵尸!”

现在考虑:
没有指定(你也不关心)究竟是什么你的朋友会选择折腾;
...但是没关系,只要:

  • 这是可以扔的东西(他不能把沙发扔给你)

  • 这是您可以抓住的东西(希望他不要扔手里剑)

  • 您可以使用它来打击僵尸的大脑(排除枕头之类的东西)

不论是棒球棒还是锤子,都没关系-
只要能满足您的三个条件,您就很好。

把它们加起来:

编写界面时,您基本上是在说:“我需要……”


13
实际上,枕头仍然可以工作。你可以用它打僵尸。重击僵尸的大脑...好吧,这是性能的问题,它从来都不是界面的一部分。
meustrus's

35

接口是您应该遵守或给予的合同,具体取决于您是实施者还是用户。


1
实际上,我在这里不喜欢无条件的长期合同,因为合同通常暗指语义或行为(如按合同设计)。并非所有接口都暗示任何语义或行为。例如,“墙上的洞”是真实世界的界面。您是将其视为窗口,垃圾还是任何东西。
乌里(Uri)2010年

3
真正。接口是一个“方法签名合同”,这意味着它可以保证实现给定的方法。它不能保证是否以任何给定的方式这样做。
Erik Funkenbusch'5

确实,您应该对该定义进行更多说明。但是接口是两个部分之间的契约。合同代码。...+1
matiasnj 2011年

18

我认为“蓝图”不是一个好词。蓝图告诉您如何构建某些东西。界面特别避免告诉您如何构建东西。

一个接口定义了您如何与一个类进行交互,即它支持什么方法。


1
我相信请求者会问什么是定义,而不是不是。:)
尤金·库列索夫

7

对我来说,接口是类的蓝图,这是最好的定义吗?

否。蓝图通常包括内部组件。但是,接口纯粹是关于类外部的可见内容……或更准确地说,是实现该接口的一系列类。

该接口包括方法的签名和常量的值,以及实现该接口的类和使用该接口的其他类之间的(通常是非正式的)“行为契约”。


6

在“编程”中,接口定义对象将具有的行为,但实际上不会指定行为。这是一个合同,可以保证某个类可以做某事。

在这里考虑这段C#代码:

using System;

public interface IGenerate
{
    int Generate();
}

// Dependencies
public class KnownNumber : IGenerate
{
    public int Generate() 
    {
        return 5;
    }   
}

public class SecretNumber : IGenerate
{
    public int Generate()
    {
        return new Random().Next(0, 10);
    }
}

// What you care about
class Game
{
    public Game(IGenerate generator) 
    {
        Console.WriteLine(generator.Generate())
    }
}

new Game(new SecretNumber());
new Game(new KnownNumber());

游戏类需要一个秘密号码。为了测试它,您想注入将用作秘密数字的内容(此原理称为控制反转)。

游戏类希望对什么将实际创建随机数持开放态度,因此将在其构造函数中询问“具有Generate方法的任何内容”。

首先,接口指定对象将提供的操作。它只是包含它的外观,但没有给出实际的实现。这只是该方法的签名。按照惯例,在C#接口中以I为前缀。这些类现在实现了IGenerate接口。这意味着编译器将确保它们都具有返回int且称为的方法Generate。现在,游戏被称为两个不同的对象,每个对象都实现了正确的界面。其他类在构建代码时会产生错误。

在这里,我注意到您使用的蓝图类比:

类通常被视为对象的蓝图。接口指定了类需要做的事情,因此可以说它确实只是类的蓝图,但是由于类不一定需要接口,所以我认为这种隐喻正在打破。将接口视为合同。按照法律要求(由编译器警察强制执行)“签名”的类,以遵守合同中的条款和条件。这意味着它将必须执行接口中指定的操作。

这都是由于某些OO语言的静态类型性质所致,就像Java或C#就是这种情况。另一方面,在Python中,使用了另一种机制:

import random

# Dependencies
class KnownNumber(object):
    def generate(self):
        return 5

class SecretNumber(object):
    def generate(self):
        return random.randint(0,10)

# What you care about
class SecretGame(object):
    def __init__(self, number_generator):
        number = number_generator.generate()
        print number

在这里,没有一个类实现接口。Python对此并不关心,因为SecretGame该类将仅尝试调用传入的任何对象。如果该对象具有generate()方法,则一切都很好。如果不是这样:KAPUTT!在编译时不会看到此错误,而在运行时会看到此错误,因此可能在程序已经部署并运行时出现。C#会在您接近之前通知您。

天真地说,使用这种机制的原因是因为在OO语言中,功能自然不是一流的公民。如您所见,KnownNumber并且仅SecretNumber包含生成数字的函数。一个人根本不需要所有的课程。因此,在Python中,可以将它们扔掉并自行选择函数:

# OO Approach
SecretGame(SecretNumber())
SecretGame(KnownNumber())

# Functional Approach

# Dependencies
class SecretGame(object):
    def __init__(self, generate):
        number =  generate()
        print number

SecretGame(lambda: random.randint(0,10))
SecretGame(lambda: 5)

Lambda只是一个函数,它被声明为“随行随行”。委托在C#中是相同的:

class Game
{
    public Game(Func<int> generate) 
    {
        Console.WriteLine(generate())
    }
}    

new Game(() => 5);
new Game(() => new Random().Next(0, 10));

旁注:在Java 7之前,后面的示例不可能像这样。在那里,接口是指定此行为的唯一方法。但是,Java 8引入了lambda表达式,因此C#示例可以非常容易地转换为Java(Func<int>成为java.util.function.IntSupplier=>成为->)。


4

从技术上讲,我将接口描述为与对象进行交互的一组方式(方法,属性,访问器...词汇取决于您使用的语言)。如果对象支持/实现接口,则可以使用接口中指定的所有方式与该对象进行交互。

语义上,接口还可以包含关于您可能会做什么或可能不会做什么的约定(例如,您可以调用方法的顺序),以及关于给定的交互方式,您可以假定对象的状态的约定,因此远。


2

我个人看到一个像模板的界面。如果接口包含方法foo()和bar()的定义,那么您就会知道使用该接口的每个类都具有方法foo()和bar()。


2

让我们考虑一个Man(用户或对象)想要完成一些工作。他将联系与公司(使用实现的类创建的现实世界对象)签订合同的中间人(接口)。由他定义的工作类型很少,哪些公司将实施并给他结果。每家公司都将以自己的方式实施工作,但结果将是相同的。这样,用户将可以使用单个界面完成其工作。我认为接口将以很少的命令充当系统的可见部分,这些命令将由实现中的内部子系统内部定义。


1

接口从内部实现中分离出对类的操作。因此,一些实现可以提供许多接口。

人们通常将其描述为该类方法中必须提供的“合同”。

这绝对不是一个蓝图,因为这也将决定实施。完整的类定义可以说是一个蓝图。


1

接口定义从其继承的类必须实现的内容。这样,多个类可以从一个接口继承,并且由于这种继承性,您可以

  • 确保接口的所有成员都在派生类中实现(即使只是引发异常)
  • 从调用者中抽象出类本身(将类的实例广播到接口,并在不需要知道实际派生类是什么的情况下与之交互)

有关更多信息,请参见http://msdn.microsoft.com/zh-cn/library/ms173156.aspx


1

我认为,接口的含义比Java中通常与之相关的含义更广泛。我将“接口”定义为具有一些常用功能的一组可用操作,这些功能允许控制/监视模块。

在这个定义中,我尝试涵盖客户端是某个模块的程序化界面和人机界面(例如GUI)。

正如其他人已经说过的那样,就输入和输出而言,接口总是有一些契约。该界面不保证有关操作的“方式”。给定当前状态,所选操作及其参数,它只能保证结果的某些属性。


1

如上所述,“合同”和“协议”的同义词是合适的。

该接口包含您可以期望由类公开的方法和属性。

因此,如果类Cheetos Bag实现了Chip Bag接口,则您应该期望a Cheetos Bag的行为与其他任何行为完全相同Chip Bag。(即,公开.attemptToOpenWithoutSpillingEverywhere()方法等)


1

常规定义-接口是一种约定,它指定实现它的类需要实现的方法。

接口的定义已随时间而改变。您是否认为Interface仅具有方法声明?静态最终变量如何?Java 5之后的默认定义如何?

接口是由于具有多个继承的Diamond问题而引入Java的,而这正是它们实际要做的。

接口是为避免多重继承问题而创建的结构,可以具有抽象方法,默认定义和静态最终变量。

http://www.quora.com/Why-does-Java-allow-static-final-variables-in-interfaces-when-they-are-only-intended-to-be-contracts



By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.