我已经在C#中学习和编码了一段时间了。但是,我仍然不知道接口的用处。他们带的东西很少。除了提供功能签名外,它们什么也不做。如果我能记住需要实现的功能的名称和签名,则不需要它们。它们只是为了确保在继承的类中实现了上述功能(在接口中)。 (不允许多重继承),然后提供解决方案,这是一个乏味的工作。

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

评论

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

忘记C#,忘记Java,忘记语言。它只是在面向对象方面进行思考。我鼓励您从罗伯特·C·马丁(Robert C. Martin),马丁·福勒(Martin Fowler),迈克尔·费瑟斯(Michael Feathers),四人帮等人那里获取一些阅读材料,因为这将有助于扩大您的思维。
我花了两年多的时间才真正了解接口的优点。我的建议是:学习设计模式。由于它们大多数都依赖于接口,因此您将很快理解它们为何如此有用。

你有很多要学习的朋友。

@ChaosPandion我们都有很多东西要学习

#1 楼


它们只是用来确保在继承的类中实现上述功能(在接口中)。


正确。这是足以证明该功能合理的好处。正如其他人所说,接口是实现某些方法,属性和事件的契约义务。静态类型语言的引人注目的好处是,编译器可以验证您的代码所依赖的合同是否确实得到满足。如果您想要一种更强大,更灵活的方式来表示合同义务,请查看最新版本的Visual Studio附带的“代码合同”功能。C#是一种很棒的语言,但是有时它给您的感觉是,首先Microsoft提出了问题(不允许多重继承),然后提供了解决方案,这是一个乏味的工作。 。

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

评论


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

–配置器
2011年9月14日在17:07

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

–配置器
2011年9月14日在17:11

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

–埃里克·利珀特
2011-09-14 17:23



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

–配置器
2011-09-14 17:27

为“我很高兴您喜欢它” +1。还有其他所有东西。

– Robert S.
2011年9月15日下午16:33

#2 楼



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

评论


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

–史蒂文·斯特里加(Steven Striga)
2011年9月20日20:21在

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

–史蒂文·杰里斯(Steven Jeuris)
2011-09-20 22:17

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

– YoungJohn
15年8月11日在14:28

这个答案真是太神奇了...

–马里奥·加西亚(Mario Garcia)
18-2-22在11:29

仅指出此答案并不能说明问题(在此特定示例中),为什么使用接口胜于继承。刚接触接口的人可能会问为什么它们不都只继承自例如提供所有插头功能的MainsPoweredDevice,插座接受从MainsPoweredDevice派生的任何内容。

–ingredient_15939
19-09-26在5:48



#3 楼


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


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

借助接口,您可以在两个类之间实现非常低的耦合。

评论


我们正在聊天中讨论这个答案。

–丹尼尔(Daniel)
2011-09-15 16:16



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

–埃里克·利珀特
2011-09-15 17:40

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

–丹尼尔(Daniel)
2011-09-15 18:26

@Daniel:是的,我想是的。我猜是六分之一,还有六分之一!

–埃里克·利珀特
2011-09-15 18:27

#4 楼

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

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

示例:您要测试依赖于数据访问服务类的类。如果该类正在与Web服务或数据库通信,则您的单元测试将无法在您的测试环境中运行(而且它已经变成了集成测试)。

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

另一方面,在绑定时,WPF和Silverlight根本无法与接口一起使用。这是一个令人讨厌的皱纹。

评论


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

–安迪
2011年9月15日下午5:17

#5 楼

接口是(静态)多态性的支柱!界面很重要。没有接口,继承将无法工作,因为子类基本上会继承父级已实现的接口。

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

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

是否要使用其他列表实现?针对接口编写代码,调用者无需担心实现!当您意识到实现不符合您的需求时更改实现。如果您尝试仅通过多重继承来实现该目标,则很麻烦,或者归结为创建空类以提供必要的接口。

评论


从未听说过多态,您是说多态吗?

–史蒂文·杰里斯(Steven Jeuris)
2011年9月14日14:13在

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

–潘卡吉·阿帕德(Pankaj Upadhyay)
2011-09-14 14:15

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

–猎鹰
2011-09-14 14:17



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

–潘卡吉·阿帕德(Pankaj Upadhyay)
2011-09-14 14:25

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

–乔纳森·汉森(Jonathan Henson)
2011-09-14 14:33

#6 楼

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

这肯定是一个具体案例,说明了接口的实用性。

评论


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

–马特H
2011-09-14 14:15

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

–潘卡吉·阿帕德(Pankaj Upadhyay)
2011年9月14日14:17在

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

–马特H
2011-09-14 14:53



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

–凯文
2011-09-14 15:05

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

–潘卡吉·阿帕德(Pankaj Upadhyay)
2011-09-14 17:34

#7 楼

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

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

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

#8 楼

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

在Python中,支持多重继承,但程序员几乎普遍误解了这一点,并声明您知道它的工作方式,这意味着要阅读和理解有关以下主题的本文:方法解析顺序。在Python中发生的另一件事是,将接口归类为该语言-Zope.Interfaces。您。 C ++专业人员知道如何使用多重继承。其他所有人通常只是在玩耍而不知道结果如何。另一个说明接口有用的事实是,在许多情况下,一个类可能需要完全覆盖其父代的行为。在这种情况下,不需要父级实现,而只会给子类增加父级私有变量的内存负担,这在C#时代可能并不重要,但在进行嵌入式编程时却很重要。如果使用接口,那么就不存在这个问题。

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

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

更新:现在,我看到我可能回答了一个不同的问题-为什么用接口代替多重继承,但这看起来像您正在寻找的答案。除了面向对象语言之外,还应至少包含两种语言之一,其他答案已经涵盖了您的原始问题。

#9 楼

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

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

#10 楼

我个人喜欢抽象类,并且比接口更多地使用它。主要区别在于与.NET接口(如IDisposable,IEnumerable等)的集成以及与COM互操作的集成。另外,与抽象类相比,编写该接口的工作量要少一些,一个类可以实现多个接口,而它只能从一个类继承。我会使用一个接口来更好地由抽象类提供服务。纯虚函数(抽象函数)使您可以强制实施者定义函数,类似于接口强制实施者定义其所有成员的方式。

但是,当您不希望将某种设计强加于超类时,通常会使用一个接口,而您将使用抽象类来具有已经实现了大部分的可重用设计。 />
我已经广泛使用接口来使用System.ComponentModel命名空间编写插件环境。它们派上用场了。

评论


很好放!我猜您会喜欢我关于抽象类的文章。抽象就是一切。

–史蒂文·杰里斯(Steven Jeuris)
2011年9月14日14:33



#11 楼

我可以说我与此有关。当我第一次开始学习面向对象和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
13年1月29日在21:32

从现在开始,我完全使用美国与英语演讲者的比喻。那太棒了。

–布莱恩·博特彻(Bryan Boettcher)
18年1月11日在20:35

用更简单的方式解释!太棒了

–动物汗
18年8月31日在2:41

#12 楼

主要有两个原因:


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


评论


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

–潘卡吉·阿帕德(Pankaj Upadhyay)
2011-09-14 14:16

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

–奥利弗·韦勒
2011-09-14 17:01

#13 楼

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

评论


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

– miraculixx
2012年12月1日下午14:25

#14 楼

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

#15 楼

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

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

#16 楼

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

#17 楼

真实的实现:

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

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


您可以创建接口列表/>
List<IHelper> HelperList = new List<IHelper>();


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

#18 楼

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

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

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

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

>
...使用:

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


有效听起来很像:


!!请了解清算的每个人现在Clear


这样,我们可以以编程方式避免告诉每件事清除自身。而且,将来添加可清除项目时,它们只需响应即可,无需任何其他代码。

#19 楼

以下是伪代码:

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 } }


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

除非可能有多重继承,否则继承会强制实现父类使事情变得更加僵化。另一方面,针对接口进行编程可以使您的代码或框架非常灵活。如果您遇到希望在继承链中交换类的情况,那么您将了解原因。
例如,提供最初打算从磁盘读取数据的Reader的框架可以重新实现,以完全相同的方式执行某些操作,但完全不同。例如解释莫尔斯电码。