为什么界面有用?


158

我已经在C#中学习和编码了一段时间了。但是,我仍然不知道接口的用处。他们带来的东西太少了。除了提供功能签名外,它们什么也不做。如果我能记住需要实现的功能的名称和签名,则不需要它们。它们只是为了确保在继承类中实现了所述功能(在接口中)。

C#是一种很棒的语言,但是有时它给您的感觉是,Microsoft首先提出了问题(不允许多重继承),然后提供了解决方案,这是一个乏味的工作。

我的理解是基于有限的编码经验。您对接口有什么看法?您多久使用一次,是什么使您这样做?


55
“如果我能记住需要实现的功能的名称和签名,就不需要它们了。” 这句话让我怀疑您应该更多地研究静态类型语言优点
Steven Jeuris 2011年

37
忘记C#,忘记Java,忘记语言。它只是在面向对象方面进行思考。我鼓励您从罗伯特·C·马丁(Robert C. Martin),马丁·福勒(Martin Fowler),迈克尔·费瑟斯(Michael Feathers),四人帮等人那里获取一些阅读材料,因为这将有助于扩大您的思维范围。
Anthony Pegram

27
我花了两年多的时间才真正了解接口的优点。我的建议是:研究设计模式。由于它们大多数依赖接口,因此您将很快理解它们为何如此有用。
奥利弗·韦勒

37
您有很多要学习的朋友。
ChaosPandion 2011年

60
@ChaosPandion我们都有很多东西要学习
kenwarner 2011年

Answers:


151

它们只是为了确保(在接口中)所述功能在继承类中实现。

正确。这是足以证明该功能合理的好处。正如其他人所说,接口是实现某些方法,属性和事件的合同义务。静态类型语言的引人注目的好处是,编译器可以验证您的代码所依赖的合同是否确实得到满足。

也就是说,接口是表示合同义务的一种相当弱的方法。如果您想要一种更强大,更灵活的方式来表示合同义务,请查看Visual Studio的最新版本附带的“代码合同”功能。

C#是一种很棒的语言,但是有时它给您的感觉是,Microsoft首先提出了问题(不允许多重继承),然后提供了解决方案,这是一个乏味的工作。

好吧,我很高兴你喜欢它。

所有复杂的软件设计都是相互权衡冲突的功能,并试图找到可以以小成本带来巨大收益的“最佳位置”的结果。我们通过痛苦的经验中学到,允许多重继承以实现实现共享的语言,收益相对较小,成本较高。只允许在不共享实现细节的接口上进行多重继承,可以带来多重继承的许多好处,而不会花费很多成本。


我刚刚读过“ Microsoft不允许多重继承会造成问题”,我认为Eric Lippert对此有话要说。
配置器

与这个答案更相关:Eric,您是在向发问者提出“代码合同”,但是这些合同并不完整。静态检查器无法执行除最基本合同以外的任何其他操作。我尝试在一个小项目上使用Code Contract;我为每种方法都添加了数百行,指定了我可以输入和输出的所有内容,但是Assume,对于数组或枚举成员之类的情况,我必须添加如此多的调用。添加完所有内容并看到我拥有的经过静态验证的混乱之后,我恢复了源代码管理,因为这降低了我的项目质量。
Configurator

17
@configurator:它们不完整,因为它们不完整;具有任意合同的静态程序验证等同于解决暂停问题。(例如,您可以编写代码约定,说方法的参数必须是Fermat的Last Theorem的反例;但是静态验证程序将无法验证是否没有这样的参数。)如果您希望静态验证程序在宇宙热死之前完成其工作,就必须明智地使用它。(如果您抱怨BCL注释不足,我同意。)
Eric Lippert

2
我希望它能意识到,当一种方法保证结果数组或枚举不包含空值时,using方法可以在不允许空值的地方使用这些值。那是我期望它做的一件事,而没有做,这太重要了,没有它就无法使用。其他任何东西都只是奖金。就是说,我知道静态验证无法完成,这就是为什么我认为依靠合同不是一个好的选择。
配置器

9
为“很高兴您喜欢它” +1。还有其他所有东西。
罗伯特·S。

235

在此处输入图片说明

因此,在此示例中,PowerSocket对其他对象一无所知。这些对象都依赖于PowerSocket提供的Power,因此它们实现了IPowerPlug,这样它们就可以连接到它。

接口非常有用,因为它们提供了对象可以用来一起工作的契约,而无需彼此了解其他任何信息。


4
左下角的对象看起来好像没有实现IPowerPlug =)
Steven Striga

100
该死,但我们必须使用适配器模式才能在不同国家使用IPowerPlug!
Steven Jeuris 2011年

杰瑞(Jerry)操纵设备在接口上工作并不安全(但有些人还是经常以性能未经测试为借口这样做),并有可能引发火灾。
YoungJohn

4
这个答案真是令人惊讶...
Mario Garcia '18

仅指出此答案并不能说明问题(在此示例中),为什么使用接口胜于继承。刚接触接口的人可能会问,为什么它们不都继承自提供所有插头功能的MainsPoweredDevice,而套接字却接受了从MainsPoweredDevice派生的任何东西。
ingredient_15939

145

除了提供功能签名外,它们什么也不做。如果我能记住需要实现的功能的名称和签名,则无需使用它们

接口的目的不是帮助您记住要实现的方法,而是在这里定义合同。在foreach P.Brian.Mackey示例事实证明是错误的,但我们不在乎)中,IEnumerable定义了foreach任何可枚举对象之间的契约。它说:“无论您是谁,只要您遵守合同(实现IEnumerable),我保证您都会遍历所有元素”。而且,这很棒(对于非动态语言)。

由于有了接口,您可以在两个类之间实现非常低的耦合。



我不喜欢使用“鸭子打字”一词,因为它对不同的人意味着不同的意思。我们将模式匹配用于“ foreach”循环,因为在设计时,IEnumerable <T>不可用。我们对LINQ使用模式匹配,因为C#类型系统太弱而无法捕获我们所需的“ monad模式”。您将需要类似Haskell类型的系统。
埃里克·利珀特

@Eric:“模式匹配”不存在相同的问题吗?当我听到它时,我认为是F#/ Scala / Haskell。但是我想这是比鸭嘴式更广泛的想法。
丹尼尔(Daniel)

@Daniel:是的,我想是的。我猜是六分之一,还有六分之一!
埃里克·利珀特

36

接口是维护良好分离的构造的最佳方法。

在编写测试时,您会发现具体的类在您的测试环境中不起作用。

示例:您想测试一个依赖于数据访问服务类的类。如果该类正在与Web服务或数据库进行对话-您的单元测试将不会在您的测试环境中运行(而且它已经变成了集成测试)。

解?为您的数据访问服务使用一个接口,然后模拟该接口,以便您可以将一个单元作为一个类进行测试。

另一方面,在绑定方面,WPF和Silverlight根本无法与Interfaces配合使用。这是一个非常讨厌的皱纹。


2
听见!发明了接口来解决其他问题,例如多态性。但是,对我来说,它们在实现依赖注入模式时会发挥作用。
安迪

29

接口是(静态)多态性的支柱!界面很重要。没有接口就无法进行继承,因为子类基本上继承了父类已经实现的接口。

您多久使用一次,是什么使您这样做的?

很经常。需要插入的所有内容都是我的应用程序中的接口。通常,您有其他不相关的类需要提供相同的行为。您无法通过继承解决此类问题。

需要不同的算法对同一数据执行操作吗?使用界面(请参阅策略模式)!

您是否要使用其他列表实现?针对接口的代码,调用者无需担心实现!

出于年龄原因,针对年龄进行接口编码已被认为是一种好习惯(不仅在OOP中),原因是:当您意识到实现不适合您时,很容易更改实现。如果您尝试仅通过多重继承来实现该目标,那么它很麻烦,或者归结为创建空类以提供必要的接口。


1
从未听说过多态,您是说多态吗?
Steven Jeuris 2011年

1
话虽这么说,如果微软首先允许多重继承,那么就没有理由存在接口了
Pankaj Upadhyay

10
@Pankaj Upadhyay:多重继承和接口是两双不同的鞋子。如果您需要两个具有不同行为的不相关类的接口,该怎么办?您无法通过多重继承解决该问题。无论如何,您必须分别实现它。然后,您将需要一些描述接口的信息,以提供多态行为。在很多情况下,多重继承是一个盲目的小巷,迟早要把自己开枪打死太容易了。
猎鹰

1
让我们简化一下。如果我的界面实现了Display和Comment这两个功能,那么我有一个实现它们的类。那为什么不删除接口并直接使用功能呢?我的意思是说,它们只是为您提供需要实现的功能的名称。如果可以记住这些功能,那么为什么要创建一个界面
Pankaj Upadhyay

9
@Pankaj,如果仅此是您需要接口,则不要使用它。当您有一个想要忽略类的各个方面并通过其基本类型(即接口)访问它的程序时,请使用接口。他们不需要知道任何子类,只需知道它是接口的类型即可。这样,您就可以通过对对象接口的引用来调用子类的已实现方法。这是基本的继承和设计内容。没有它,您也可能会使用C。即使您没有显式使用它,如果没有它,该框架也无法工作。
乔纳森·亨森

12

您可能已经使用foreach并发现它是一个非常有用的迭代工具。您是否知道它需要接口才能运行IEnumerable

对于接口的有用性,这肯定是一个具体案例。


5
实际上,foreach不需要IEnumerable:msdn.microsoft.com/en-us/library/9yb8xew9%28VS.80%29.aspx
Matt H

1
我已经研究了所有这些,但是就像用另一只手握住耳朵。如果允许多重继承,则接口将是一个遥不可及的选择。
Pankaj Upadhyay

2
接口是多重继承,通常会被遗忘。但是,它们不允许行为和状态的多重继承。混合(mixin)或特征允许行为的多重继承,但不允许导致问题的共享状态:en.wikipedia.org/wiki/Mixin
Matt H

@Pankaj,题外话,但是您介意我问您的母语是什么吗?“用另一只手握住耳朵”是个好习惯,我很好奇它的来历。
凯文

3
@冰人。大声笑...。我来自印度。这是一个常见的成语,反映了以困难的方式做简单的事情。
Pankaj Upadhyay

11

接口用于编码对象,就像插头用于家庭布线一样。您可以将收音机直接焊接到房屋布线上吗?真空吸尘器怎么样?当然不是。插头及其所插入的插座形成了房屋布线和需要电源的设备之间的“接口”。除了使用三脚接地插头并需要120VAC <= 15A的电源外,您的房屋布线对设备一无所知。相反,该设备除了具有一个或多个方便提供120VAC <= 15A的三插脚插座之外,不需要任何关于房屋布线的奥秘知识。

接口在代码中执行非常相似的功能。对象可以声明特定的变量,参数或返回类型为接口类型。无法使用new关键字直接实例化该接口,但是可以为我的对象提供或找到需要使用该接口的实现。对象具有其依赖关系后,就不必确切知道该依赖关系是什么,它只需要知道它可以在依赖关系上调用方法X,Y和Z。接口的实现不必知道如何使用它们,而只需要知道它们将为方法X,Y和Z提供特定的签名即可。

因此,通过在同一接口后面抽象多个对象,可以为该接口对象的任何使用者提供一组通用功能。您不必知道对象是例如List,Dictionary,LinkedList,OrderedList或其他对象。因为您知道所有这些都是IEnumerable,所以可以使用IEnumerable的方法一次遍历这些集合中的每个元素。您不必知道输出类是ConsoleWriter,FileWriter,NetworkStreamWriter甚至是采用其他类型编写器的MulticastWriter。您只需要知道它们都是IWriter(或其他任何IWriters),因此它们具有“写入”方法,您可以将字符串传递到该方法中,然后将输出该字符串。


7

虽然显然(至少起初)让程序员拥有多重继承是一种享受,但这几乎是微不足道的,并且(在大多数情况下)您不应该依赖多重继承。造成这种情况的原因很复杂,但是,如果您真的想了解它,请考虑两种最支持它的编程语言(按TIOBE index的经验):C ++和Python(分别为第3和第8)。

在Python中,支持多重继承,但程序员几乎普遍误解了这一点,并且指出您知道它的工作方式,意味着阅读和理解有关此主题的本文:方法解析顺序。在Python中发生的其他事情是,接口与Zope.Interfaces语言是一类的接口。

对于C ++,Google“钻石层次C ++”并注视着将要覆盖的丑陋之处。C ++专业人员知道如何使用多重继承。其他所有人通常只是在玩耍而不知道结果会是什么。另一个说明接口有用性的事实是,在许多情况下,一个类可能需要完全覆盖其父代的行为。在这种情况下,不需要父级实现,而只会给子类增加父级私有变量的内存,这在C#时代可能并不重要,但在进行嵌入式编程时却很重要。如果使用接口,则该问题不存在。

总之,我认为接口是OOP的重要组成部分,因为它们可以执行合同。多重继承在有限的情况下很有用,通常只对知道如何使用它的人有用。因此,如果您是初学者,那么您会因为缺乏多重继承而受到对待-这为您提供了一个更好的机会,不会犯错

同样,从历史上看,关于接口的想法植根于Microsoft的C#设计规范。大多数人认为C#是Java的升级(在大多数意义上),并猜测C#从Java那里获得了接口。协议是同一个概念的旧词,它比.NET老。

更新:现在我看到我可能已经回答了一个不同的问题-为什么要使用接口而不是多重继承,但这似乎就是您要寻找的答案。除OO语言外,还应至少包含两种语言之一,其他答案已涵盖了您的原始问题。


6

如果不使用接口,很难想象干净的,面向对象的C#代码。每当您希望加强某些功能的可用性而不必强迫类从特定的基类继承时,就可以使用它们,这使您的代码具有相关的(低)耦合级别。

我不同意多重继承比拥有接口更好,甚至在我们争论多重继承伴随着它自己的痛苦之前。接口是实现多态性和代码重用的基本工具,还需要什么?


5

我个人喜欢抽象类,并且比接口更多地使用它。主要区别在于与.NET接口(例如IDisposable,IEnumerable等)的集成以及与COM互操作的集成。而且,与抽象类相比,编写该接口的工作量要少一些,一个类可以实现多个接口,而它只能从一个类继承。

就是说,我发现抽象类可以更好地为接口使用大多数东西。纯虚函数(抽象函数)使您可以强制实现者定义函数,类似于接口强制实现者定义其所有成员的方式。

但是,当您不希望在超类上强加某种设计时,通常会使用一个接口,而您将使用抽象类来拥有已经被大部分实现的可重用设计。

我在使用System.ComponentModel命名空间编写插件环境中广泛使用了接口。他们派上用场了。


1
很好放!我猜你会喜欢我关于抽象类的文章。抽象就是一切
Steven Jeuris 2011年

5

我可以说我与此有关。当我第一次开始学习面向对象和C#时,我也没有得到接口。没关系。我们只需要遇到一些会使您欣赏到界面便利性的东西。

让我尝试两种方法。请原谅我。

尝试1

假设您是说英语的人。您去了英语不是母语的另一个国家。你需要帮助。您需要可以帮助您的人。

您是否会问:“嘿,您是在美国出生的吗?” 这是继承。

还是您问:“嘿,您会说英语吗?” 这是界面。

如果您关心它的作用,则可以依赖接口。如果您关心的是什么,那么您将依靠继承。

可以依赖继承。如果您需要说英语的人,喜欢喝茶,喜欢足球,最好是要求英国人。:)

试试2

好的,让我们尝试另一个示例。

您使用不同的数据库,并且需要实现抽象类才能使用它们。您将把您的课程传递给数据库供应商的某些课程。

public abstract class SuperDatabaseHelper
{
   void Connect (string User, string Password)
}

public abstract class HiperDatabaseHelper
{
   void Connect (string Password, string User)
}

您说多重继承?尝试以上情况。你不能 编译器不会知道您要尝试调用的Connect方法。

interface ISuperDatabaseHelper
{
  void Connect (string User, string Password)
}

interface IHiperDatabaseHelper
{
   void Connect (string Password, string User)
}

现在,我们可以使用某些东西-至少在C#中-我们可以在其中明确实现接口。

public class MyDatabaseHelper : ISuperDatabaseHelper, IHiperDatabaseHelper
{
   IHiperDataBaseHelper.Connect(string Password, string User)
   {
      //
   }

   ISuperDataBaseHelper.Connect(string User, string Password)
   {
      //
   }

}

结论

例子不是最好的,但我认为这很重要。

当您感到需要时,您只会“获取”接口。直到他们,你会认为他们不适合你。


第一次尝试是让我投票的原因。
osundblad

1
从现在开始,我完全使用美国说话者和英语说话者的比喻。那太棒了。
Bryan Boettcher '18

用更简单的方式解释!太棒了
阿里姆·汗

4

有两个主要原因:

  1. 缺乏多重继承。您可以从一个基类继承并实现任意数量的接口。这是在.NET中“做”多重继承的唯一方法。
  2. COM互操作性。“较旧”技术需要使用的所有内容都需要定义接口。

要点1绝对是Microsoft开发人员自己开发的原因和原因
Pankaj Upadhyay

1
@Pankja实际上,他们采用了Java的接口思想(就像C#的大部分功能一样)。
奥利弗·韦勒

3

使用接口有助于使系统保持解耦状态,从而更易于重构,更改和重新部署。这是面向对象正统的一个非常核心的概念,当C ++专家创建与接口相当的“纯抽象类”时,我首先了解了它。


去耦很重要,因为它使系统的不同组件彼此独立。即使一个组件的影响很大,也不会影响到其他组件。将电源插头视为与公用事业公司的接口(指定电压以及插头的物理引脚和格式)。借助此界面,公用事业公司可以完全改变其发电方式(例如,使用太阳能技术),但是没有任何设备会注意到更不用说改变了。
miraculixx

3

接口本身不是很有用。但是,当通过具体的类实现时,您会看到它为您提供了一个或多个实现的灵活性。额外的好处是使用接口的对象不需要知道实际实现的细节,这就是所谓的封装。


2

它们主要用于代码可重用性。如果您对接口进行编码,则可以使用从该接口继承的不同类,而不破坏所有内容。

此外,它们在Web服务中非常有用,您想让客户端知道类的作用(以便他们可以使用它),但又不想给他们实际的代码。


2

作为一个年轻的程序员/开发人员,仅学习C#可能不会看到接口的用处,因为您可能使用类编写代码并且代码工作正常,但是在现实生活中,构建可伸缩,健壮和可维护的应用程序需要使用一些架构和模式,只能通过使用接口才能实现,例如依赖注入。


1

实际的实现:

您可以将对象转换为接口类型:

IHelper h = (IHelper)o;
h.HelperMethod();

您可以创建接口列表

List<IHelper> HelperList = new List<IHelper>();

使用这些对象,您可以访问任何接口方法或属性。通过这种方式,您可以为程序的一部分定义接口。并围绕它建立逻辑。然后其他人可以在其业务对象中实现您的界面。如果BO发生更改,他们可以更改接口组件的逻辑,而无需更改您的作品的逻辑。


0

接口通过为类提供一种机制来理解(或订阅)系统传递的某些类型的消息,从而赋予了插件式的模块化性。我会详细说明。

在您的应用程序中,您决定每次加载或重新加载表单时都希望清除其承载的所有内容。您定义一个IClear实现的接口Clear。此外,您决定只要用户单击保存按钮,表单就应尝试保持其状态。因此,所有遵守的内容ISave都会收到一条消息以保持其状态。当然,实际上,大多数接口都处理多个消息。

使接口与众不同的是,无需继承即可实现常见行为。实现给定接口的类仅了解发出命令时的行为(命令消息)或查询时如何响应(查询消息)。本质上,应用程序中的类可以理解应用程序提供的消息。这使得构建可插入事物的模块化系统更加容易。

在大多数语言中,都有一些机制(如LINQ)来查询遵守接口的事物。通常,这将帮助您消除条件逻辑,因为您不必告诉不同的事物(不必从同一继承链派生)如何表现出相似的行为(根据特定的消息)。而是,您收集所有了解特定消息的内容(遵循界面)并发布该消息。

例如,您可以替换...

Me.PublishDate.Clear()
Me.Subject.Clear()
Me.Body.Clear()

...具有:

For Each ctl As IClear In Me.Controls.OfType(Of IClear)()
    ctl.Clear()
Next

实际上听起来很像:

听听,听听!Clear现在,请了解结算的所有人都可以!

这样,我们可以以编程方式避免告诉每件事清除自身。而且,将来添加可清除项目时,它们只是在没有任何其他代码的情况下做出响应。


0

以下是伪代码:

class MyClass{

    private MyInterface = new MyInterfaceImplementationB();

    // Code using Thingy 

}

interface MyInterface{

    myMethod();

}

class MyInterfaceImplementationA{ myMethod(){ // method implementation A } }

class MyInterfaceImplementationB{ myMethod(){ // method implementation B } }

class MyInterfaceImplementationC{ myMethod(){ // method implementation C } }

最后的类可以是完全不同的实现。

除非可能有多重继承,否则继承会强加父类的实现,从而使事情更加僵化。另一方面,针对接口进行编程可以使您的代码或框架非常灵活。如果遇到您希望可以在继承链中的类之间进行交换的情况,您将理解原因。

例如,可以重新实现提供最初旨在从磁盘读取数据的读取器的框架,以执行相同性质但完全不同的方式。例如解释莫尔斯电码。

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.