我已经在C#中学习和编码了一段时间了。但是,我仍然不知道接口的用处。他们带来的东西太少了。除了提供功能签名外,它们什么也不做。如果我能记住需要实现的功能的名称和签名,则不需要它们。它们只是为了确保在继承类中实现了所述功能(在接口中)。
C#是一种很棒的语言,但是有时它给您的感觉是,Microsoft首先提出了问题(不允许多重继承),然后提供了解决方案,这是一个乏味的工作。
我的理解是基于有限的编码经验。您对接口有什么看法?您多久使用一次,是什么使您这样做?
我已经在C#中学习和编码了一段时间了。但是,我仍然不知道接口的用处。他们带来的东西太少了。除了提供功能签名外,它们什么也不做。如果我能记住需要实现的功能的名称和签名,则不需要它们。它们只是为了确保在继承类中实现了所述功能(在接口中)。
C#是一种很棒的语言,但是有时它给您的感觉是,Microsoft首先提出了问题(不允许多重继承),然后提供了解决方案,这是一个乏味的工作。
我的理解是基于有限的编码经验。您对接口有什么看法?您多久使用一次,是什么使您这样做?
Answers:
它们只是为了确保(在接口中)所述功能在继承类中实现。
正确。这是足以证明该功能合理的好处。正如其他人所说,接口是实现某些方法,属性和事件的合同义务。静态类型语言的引人注目的好处是,编译器可以验证您的代码所依赖的合同是否确实得到满足。
也就是说,接口是表示合同义务的一种相当弱的方法。如果您想要一种更强大,更灵活的方式来表示合同义务,请查看Visual Studio的最新版本附带的“代码合同”功能。
C#是一种很棒的语言,但是有时它给您的感觉是,Microsoft首先提出了问题(不允许多重继承),然后提供了解决方案,这是一个乏味的工作。
好吧,我很高兴你喜欢它。
所有复杂的软件设计都是相互权衡冲突的功能,并试图找到可以以小成本带来巨大收益的“最佳位置”的结果。我们通过痛苦的经验中学到,允许多重继承以实现实现共享的语言,其收益相对较小,成本较高。只允许在不共享实现细节的接口上进行多重继承,可以带来多重继承的许多好处,而不会花费很多成本。
Assume
,对于数组或枚举成员之类的情况,我必须添加如此多的调用。添加完所有内容并看到我拥有的经过静态验证的混乱之后,我恢复了源代码管理,因为这降低了我的项目质量。
因此,在此示例中,PowerSocket对其他对象一无所知。这些对象都依赖于PowerSocket提供的Power,因此它们实现了IPowerPlug,这样它们就可以连接到它。
接口非常有用,因为它们提供了对象可以用来一起工作的契约,而无需彼此了解其他任何信息。
除了提供功能签名外,它们什么也不做。如果我能记住需要实现的功能的名称和签名,则无需使用它们
接口的目的不是帮助您记住要实现的方法,而是在这里定义合同。在foreach P.Brian.Mackey示例(事实证明是错误的,但我们不在乎)中,IEnumerable定义了foreach与任何可枚举对象之间的契约。它说:“无论您是谁,只要您遵守合同(实现IEnumerable),我保证您都会遍历所有元素”。而且,这很棒(对于非动态语言)。
由于有了接口,您可以在两个类之间实现非常低的耦合。
接口是维护良好分离的构造的最佳方法。
在编写测试时,您会发现具体的类在您的测试环境中不起作用。
示例:您想测试一个依赖于数据访问服务类的类。如果该类正在与Web服务或数据库进行对话-您的单元测试将不会在您的测试环境中运行(而且它已经变成了集成测试)。
解?为您的数据访问服务使用一个接口,然后模拟该接口,以便您可以将一个单元作为一个类进行测试。
另一方面,在绑定方面,WPF和Silverlight根本无法与Interfaces配合使用。这是一个非常讨厌的皱纹。
接口是(静态)多态性的支柱!界面很重要。没有接口就无法进行继承,因为子类基本上继承了父类已经实现的接口。
您多久使用一次,是什么使您这样做的?
很经常。需要插入的所有内容都是我的应用程序中的接口。通常,您有其他不相关的类需要提供相同的行为。您无法通过继承解决此类问题。
需要不同的算法对同一数据执行操作吗?使用界面(请参阅策略模式)!
您是否要使用其他列表实现?针对接口的代码,调用者无需担心实现!
出于年龄原因,针对年龄进行接口编码已被认为是一种好习惯(不仅在OOP中),原因是:当您意识到实现不适合您时,很容易更改实现。如果您尝试仅通过多重继承来实现该目标,那么它很麻烦,或者归结为创建空类以提供必要的接口。
您可能已经使用foreach
并发现它是一个非常有用的迭代工具。您是否知道它需要接口才能运行IEnumerable?
对于接口的有用性,这肯定是一个具体案例。
接口用于编码对象,就像插头用于家庭布线一样。您可以将收音机直接焊接到房屋布线上吗?真空吸尘器怎么样?当然不是。插头及其所插入的插座形成了房屋布线和需要电源的设备之间的“接口”。除了使用三脚接地插头并需要120VAC <= 15A的电源外,您的房屋布线对设备一无所知。相反,该设备除了具有一个或多个方便提供120VAC <= 15A的三插脚插座之外,不需要任何关于房屋布线的奥秘知识。
接口在代码中执行非常相似的功能。对象可以声明特定的变量,参数或返回类型为接口类型。无法使用new
关键字直接实例化该接口,但是可以为我的对象提供或找到需要使用该接口的实现。对象具有其依赖关系后,就不必确切知道该依赖关系是什么,它只需要知道它可以在依赖关系上调用方法X,Y和Z。接口的实现不必知道如何使用它们,而只需要知道它们将为方法X,Y和Z提供特定的签名即可。
因此,通过在同一接口后面抽象多个对象,可以为该接口对象的任何使用者提供一组通用功能。您不必知道对象是例如List,Dictionary,LinkedList,OrderedList或其他对象。因为您知道所有这些都是IEnumerable,所以可以使用IEnumerable的方法一次遍历这些集合中的每个元素。您不必知道输出类是ConsoleWriter,FileWriter,NetworkStreamWriter甚至是采用其他类型编写器的MulticastWriter。您只需要知道它们都是IWriter(或其他任何IWriters),因此它们具有“写入”方法,您可以将字符串传递到该方法中,然后将输出该字符串。
虽然显然(至少起初)让程序员拥有多重继承是一种享受,但这几乎是微不足道的,并且(在大多数情况下)您不应该依赖多重继承。造成这种情况的原因很复杂,但是,如果您真的想了解它,请考虑两种最支持它的编程语言(按TIOBE index的经验):C ++和Python(分别为第3和第8)。
在Python中,支持多重继承,但程序员几乎普遍误解了这一点,并且指出您知道它的工作方式,意味着阅读和理解有关此主题的本文:方法解析顺序。在Python中发生的其他事情是,接口与Zope.Interfaces语言是一类的接口。
对于C ++,Google“钻石层次C ++”并注视着将要覆盖的丑陋之处。C ++专业人员知道如何使用多重继承。其他所有人通常只是在玩耍而不知道结果会是什么。另一个说明接口有用性的事实是,在许多情况下,一个类可能需要完全覆盖其父代的行为。在这种情况下,不需要父级实现,而只会给子类增加父级私有变量的内存,这在C#时代可能并不重要,但在进行嵌入式编程时却很重要。如果使用接口,则该问题不存在。
总之,我认为接口是OOP的重要组成部分,因为它们可以执行合同。多重继承在有限的情况下很有用,通常只对知道如何使用它的人有用。因此,如果您是初学者,那么您会因为缺乏多重继承而受到对待-这为您提供了一个更好的机会,不会犯错。
同样,从历史上看,关于接口的想法植根于Microsoft的C#设计规范。大多数人认为C#是Java的升级(在大多数意义上),并猜测C#从Java那里获得了接口。协议是同一个概念的旧词,它比.NET老。
更新:现在我看到我可能已经回答了一个不同的问题-为什么要使用接口而不是多重继承,但这似乎就是您要寻找的答案。除OO语言外,还应至少包含两种语言之一,其他答案已涵盖了您的原始问题。
我个人喜欢抽象类,并且比接口更多地使用它。主要区别在于与.NET接口(例如IDisposable,IEnumerable等)的集成以及与COM互操作的集成。而且,与抽象类相比,编写该接口的工作量要少一些,一个类可以实现多个接口,而它只能从一个类继承。
就是说,我发现抽象类可以更好地为接口使用大多数东西。纯虚函数(抽象函数)使您可以强制实现者定义函数,类似于接口强制实现者定义其所有成员的方式。
但是,当您不希望在超类上强加某种设计时,通常会使用一个接口,而您将使用抽象类来拥有已经被大部分实现的可重用设计。
我在使用System.ComponentModel命名空间编写插件环境中广泛使用了接口。他们派上用场了。
我可以说我与此有关。当我第一次开始学习面向对象和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)
{
//
}
}
结论
例子不是最好的,但我认为这很重要。
当您感到需要时,您只会“获取”接口。直到他们,你会认为他们不适合你。
有两个主要原因:
使用接口有助于使系统保持解耦状态,从而更易于重构,更改和重新部署。这是面向对象正统的一个非常核心的概念,当C ++专家创建与接口相当的“纯抽象类”时,我首先了解了它。
接口通过为类提供一种机制来理解(或订阅)系统传递的某些类型的消息,从而赋予了插件式的模块化性。我会详细说明。
在您的应用程序中,您决定每次加载或重新加载表单时都希望清除其承载的所有内容。您定义一个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
现在,请了解结算的所有人都可以!
这样,我们可以以编程方式避免告诉每件事清除自身。而且,将来添加可清除项目时,它们只是在没有任何其他代码的情况下做出响应。
以下是伪代码:
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 } }
最后的类可以是完全不同的实现。
除非可能有多重继承,否则继承会强加父类的实现,从而使事情更加僵化。另一方面,针对接口进行编程可以使您的代码或框架非常灵活。如果遇到您希望可以在继承链中的类之间进行交换的情况,您将理解原因。
例如,可以重新实现提供最初旨在从磁盘读取数据的读取器的框架,以执行相同性质但完全不同的方式。例如解释莫尔斯电码。