我将如何知道何时创build一个接口?

我在我的发展学习的一个点,我觉得我必须学习更多的接口。

我经常读到他们,但似乎我无法把握他们。

我读过的例子如下:动物基类,IAnimal接口,如“走”,“运行”,“GetLegs”等 – 但我从来没有在工作的东西,感觉像“嘿我应该使用一个接口这里!”

我错过了什么? 为什么这是一个难以理解的概念! 我只是因为我可能永远不会意识到一个具体的需要而感到害怕 – 主要是由于理解它们的一些缺失的方面! 这让我觉得我在开发人员方面失去了一些顶级的东西! 如果任何人有这样的经验,并取得突破,我将不胜感激关于如何理解这个概念的一些技巧。 谢谢。

它解决了这个具体问题:

你有四种不同types的a,b,c,d。 遍布你的代码你有这样的东西:

a.Process(); b.Process(); c.Process(); d.Process(); 

为什么不让他们实现IProcessable,然后呢

 List<IProcessable> list; foreach(IProcessable p in list) p.Process(); 

当你添加50种types的类都可以做同样的事时,这将会更好。


另一个具体问题:

你有没有看过System.Linq.Enumerable? 它定义了大量的扩展方法,可以在任何实现了IEnumerable的types上运行。 因为任何实现IEnumerable的东西基本上都是这样说的:“我支持在无序的foreach-type模式下迭代”,你可以为任何枚举types定义复杂的行为(Count,Max,Where,Select等)。

我很喜欢Jimmy的回答,但我觉得我需要添加一些东西。 整个事情的关键是能够在IProcess中“能干”。 它表示实现接口的对象的能力(或属性,但意味着“内在质量”,而不是C#属性的含义)。 IAnimal可能不是一个界面的好例子,但是IWalkable可能是一个很好的界面,如果你的系统有很多事情可以走的话。 你可能有从动物派生的类,如狗,牛,鱼,蛇。 前两个可能会实现IWalkable,后两个不走,所以他们不会。 现在你问:“为什么不只是有另一个超级动物,行走动物,狗和牛来自?”。 答案是当你有一些东西完全在inheritance树之外,也可以走,比如一个机器人。 机器人将实施IWalkable,但可能不会从动物派生。 如果你想要一个可以行走的东西的列表,你可以input它作为IWalkable,你可以把所有行走的动物加上机器人列表中。

现在用IWersablereplace更多的软件,比如IPersistable,这个类比变得更接近你在真实程序中看到的东西。

相同function的实现不同时使用接口。

当需要共享一个通用的具体实现时,使用抽象/基类。

想想像一个合同的界面。 这是一种说法,“这些类应该遵循这些规则。”

所以在IAnimal的例子中,可以这样说:“我必须能够在实现IAnimal的类上调用Run,Walk等。

为什么这是有用的? 您可能需要构build一个函数,这个函数依赖于您必须能够在对象上调用Run和Walk等function。 你可以有以下几点:

 public void RunThenWalk(Monkey m) { m.Run(); m.Walk(); } public void RunThenWalk(Dog d) { d.Run(); d.Walk(); } 

…并重复你知道的所有物体可以跑步和走路。 但是,使用您的IAnimal接口,您可以按如下方式定义一次函数:

 public void RunThenWalk(IAnimal a) { a.Run(); a.Walk(); } 

通过对接口进行编程,实质上就是相信这些类来实现接口的意图。 所以在我们的例子中,我的想法是“我不在乎他们如何跑步和走路,只要他们跑步和走路,只要他们达成了协议,我的跑步者跑步将是有效的。class上。”

在这个相关的问题上也有很好的讨论。

别担心。 很多开发人员,很less需要编写一个接口。 您经常会使用.NET框架中提供的接口,但是如果您不觉得有必要很快写出一个接口,那就没有什么奇怪的了。

我总是给一个人的例子是,如果你有一个帆船class和一个Vi蛇class。 他们分别inheritance了Boat类和Car类。 现在说,你需要遍历所有这些对象,并调用他们的Drive()方法。 虽然你可以写下如下的代码:

 if(myObject is Boat) ((Boat)myObject).Drive() else if (myObject is Car) ((Car)myObject).Drive() 

写下来会简单得多:

 ((IDrivable)myObject).Drive() 

吉米是正确的,当你想能够使用多个types的单个variables,但所有这些types通过接口声明实现相同的方法。 然后你可以在接口typesvariables上调用它们的main方法。

然而,使用接口还有另一个原因。 当项目架构师与实现编码人员不同时,或者有多个实现编码人员和一个项目经理。 负责人可以编写一大堆接口,看看系统是否可以互操作,然后留给开发人员来填写接口和实现类。 这是确保多人编写兼容类的最好方法,并且可以并行执行。

我喜欢军队的比喻。

军士不在乎你是软件开发者音乐家还是律师
你被视为士兵

UML

警官不会打扰他正在工作的人的具体细节,
把每个人都当作士兵的抽象(…当他们不能像他们那样行事的时候惩罚他们)。

人们像士兵一样的能力被称为多态。

接口是有助于实现多态的软件结构。

需要抽象的细节才能达到简单才是回答你的问题。

多义性在词源学上意味着“多种forms”,就是把基类的任何子类的对象看作是基类的对象的能力。 因此,基类具有许多forms:基类本身及其任何子类。

(..)这使得你的代码更容易编写,也更容易让其他人理解。 它也使得你的代码具有可扩展性,因为其他子类可以稍后添加到types族中,而这些新子类的对象也可以使用现有的代码。

根据我的经验,创build接口的驱动力不会发生,直到我开始用模拟框架进行unit testing。 很明显,使用接口会使嘲笑变得更容易(因为框架依赖于虚拟方法)。 一旦我开始,我看到了从实现中抽象出我的类的接口的价值。 即使我不创build一个实际的接口,我现在尝试使我的方法是虚拟的(提供一个可以被覆盖的隐式接口)。

我发现还有许多其他的原因来加强对接口重构的良好实践,但unit testing/嘲弄的事情是提供了实践经验的首要“时刻”。

编辑 :澄清,unit testing和嘲笑我总是有两个实现 – 真正的具体实现和testing中使用的替代模拟实现。 一旦你有两个实现,接口的价值就变得很明显 – 在接口方面处理它,所以你可以随时replace实现。 在这种情况下,我用模拟接口replace它。 我知道,如果我的类是正确构build的,我可以在没有实际接口的情况下做到这一点,但使用实际的接口会加强这一点,并使其更清晰(对读者更清晰)。 没有这个动力,我不认为我会欣赏接口的价值,因为我的大多数类只有一个具体的实现。

一些非编程示例可能会帮助您在编程中看到接口的正确使用。

电气设备和电力networking之间有一个接口 – 这是关于插头和sockets的形状以及它们之间的电压/电stream的一系列惯例 。 如果你想实现一个新的电子设备,只要你的插件遵循规则,它将能够从networking获得服务。 这使得扩展性非常容易,并且消除或降低了协调成本 :您不必通知电力供应商您的新设备是如何工作的,并且就如何将新设备插入networking达成单独的协议。

国家有标准的铁路仪表。 这使得放下铁轨的工程公司和build造列车的工程公司之间的分工可以在铁路上运行,并且使得铁路公司可以在不重新整理整个系统的情况下更换和升级列车。

业务呈现给客户的服务可以被描述为一个接口:一个明确定义的接口强调服务并隐藏手段 。 当你在信箱里写信时,你希望邮政系统在给定的时间内发送信件,但是你对信件的传递方式没有任何期望: 你不需要知道邮件服务是否具有灵活性select最符合要求和当前情况的交付方式。 一个例外是客户select航空邮件的能力 – 这不是现代计算机程序员devise的那种接口,因为它揭示了太多的实现。

自然的例子:我不太喜欢吃(),makeSound(),移动()等例子。 他们确实描述了行为,这是正确的,但是他们没有描述交互以及如何启用 。 能够在大自然中进行交互的界面的明显例子是与繁殖有关,例如花朵为蜜蜂提供了一定的接口,从而可以进行授粉。

作为.net开发人员完成整个生活是完全可能的,不要写自己的界面。 毕竟,我们在没有他们的情况下幸存了好几十年,我们的语言仍然是图灵完备的。

我不能告诉你为什么你需要接口,但是我可以给你一个在我们目前的项目中使用它们的列表:

  1. 在我们的插件模型中,我们通过接口加载插件,并为插件编写器提供符合的接口。

  2. 在我们的intermachine消息系统中,消息类都实现了一个特定的接口,并使用接口“展开”。

  3. 我们的configurationpipe理系统定义了一个用于设置和检索configuration设置的接口。

  4. 我们使用一个接口来避免讨厌的循环引用问题。 (如果你不需要,不要这样做。)

我想如果有一条规则,当你想在一个is-a关系中对几个类进行分组时,就是使用接口,但是你不想在基类中提供任何实现。

一个代码示例(安德鲁与我的额外的接口的目的 )的组合,也是为什么接口,而不是一个抽象类的语言不支持多inheritance(c#和JAVA):

 interface ILogger { void Log(); } class FileLogger : ILogger { public void Log() { } } class DataBaseLogger : ILogger { public void Log() { } } public class MySpecialLogger : SpecialLoggerBase, ILogger { public void Log() { } } 

请注意,FileLogger和DataBaseLogger不需要接口(可以是Logger抽象基类)。 但是考虑你需要使用第三方logging器来强制你使用一个基类(可以说它暴露了你需要使用的保护方法)。 由于该语言不支持多重inheritance,因此您将无法使用抽象基类方法。

底线是:尽可能使用接口,以获得额外的代码灵活性。 你的实现不太受限制,所以它适应更好的变化。

我现在使用的接口,然后这里是我最新的用法(名称已被推广):

我在WinForm上有一堆需要将数据保存到业务对象的自定义控件。 一种方法是分别调用每个控件:

 myBusinessObject.Save(controlA.Data); myBusinessObject.Save(controlB.Data); myBusinessObject.Save(controlC.Data); 

这个实现的问题是,任何时候我添加一个控件,我必须进入我的“保存数据”方法,并添加新的控件。

我改变了我的控件来实现一个具有方法SaveToBusinessObject(…)的ISaveable接口,所以现在我的“保存数据”方法只是迭代通过控件,如果它发现一个ISaveable,它调用SaveToBusinessObject。 所以现在当需要一个新的控件的时候,所有需要做的事情就是在那个对象中实现ISaveable(并且永远不会碰到另外一个类)。

 foreach(Control c in Controls) { ISaveable s = c as ISaveable; if( s != null ) s.SaveToBusinessObject(myBusinessObject); } 

对于接口常常没有实现的好处是您可以对修改进行本地化。 一旦定义,您将很less更改应用程序的整体stream程,但您通常会在细节级别上进行更改。 在特定对象中保留细节时,ProcessA中的更改不会影响ProcessB中的更改。 (基类也给你这个好处。)

编辑:另一个好处是行动的特异性。 就像在我的例子中,我想要做的就是保存数据。 我不在乎它是什么types的控制或者是否可以做其他任何事情 – 我只想知道我是否可以将数据保存在控件中。 它使我的保存代码非常清晰 – 没有检查,看它是文本,数字,布尔或任何因为自定义控制处理所有这一切。

一旦你需要为你的class级强制行为,你应该定义一个接口。

动物的行为可能涉及散步,吃饭,跑步等。因此,您将它们定义为界面。

另一个实际的例子是ActionListener(或Runnable)接口。 当你需要跟踪一个特定的事件时,你会实现它们。 因此,您需要为您的类(或子类)中的actionPerformed(Event e)方法提供实现。 同样,对于Runnable接口,您提供了public void run()方法的实现。

另外,您可以通过任意数量的类来实现这些接口。

另一个使用接口的实例(在Java中)是实现C ++提供的多重inheritance。

最简单的例子就是支付处理器(PayPal,PDS等)。

假设您创build一个具有ProcessACH和ProcessCreditCard方法的接口IPaymentProcessor。

你现在可以实现一个具体的Paypal实现。 使这些方法调用PayPal特定的function。

如果您稍后决定需要切换到另一个提供商,则可以。 只需为新的提供者创build另一个具体的实现。 由于所有你绑定的是你的接口(契约),你可以换掉你的应用程序使用哪一个,而不需要改变消耗它的代码。

它也允许你执行模拟unit testing(.Net)。 如果你的类使用了一个接口,你可以在你的unit testing中模拟这个对象,并轻松地testing逻辑(而不需要实际访问数据库或Web服务等)。

http://www.nmock.org/

如果您浏览.NET Framework程序集并向下钻取任何标准对象的基类,则会注意到许多接口(成员名称为ISomeName)。

接口基本上是为了实现大或小的框架。 直到我想写一个我自己的框架,我对接口也有同感。 我还发现理解界面帮助我更快地学习框架。 当你想为任何东西写一个更优雅的解决scheme的时候,你会发现一个接口很有意义。 这就像让class上穿上合适的工作服的一种方法。 更重要的是,接口允许系统变得更加自我logging,因为当类实现接口时,复杂对象变得不太复杂,这有助于对其function进行分类。

当他们希望能够明确或隐含地参与框架时,类实现接口。 例如,IDisposable是一个通用的接口,为stream行和有用的Dispose()方法提供方法签名。 在一个框架中,您或其他开发人员需要知道的一个类是,如果它实现了IDisposable,那么您知道((IDisposable)myObject).Dispose()可用于清理目的。

经典示例:如果不实现IDisposable接口,则不能在C#中使用“using()”关键字构造,因为它要求将指定为参数的任何对象隐式转换为IDisposable。

复杂的例子:一个更复杂的例子是System.ComponentModel.Component类。 这个类实现了IDisposable和IComponent。 大多数(如果不是全部的话)具有与它们相关的可视化devise器的.NET对象实现IComponent,以便IDE能够与组件交互。

结论:随着您对.NET Framework的熟悉程度越来越高,在对象浏览器或.NET Reflector(免费)工具中遇到新类时,您将首先执行此操作( http://www.red-gate.com / products / reflector / )是检查它是从哪个类inheritance的,还有它实现的接口。 .NET Reflector比对象浏览器更好,因为它可以让你看到Derived类。 这使您可以了解从特定类派生的所有对象,从而有可能了解您不知道的框架function。 当更新或新的名称空间添加到.NET Framework时,这一点尤为重要。

假设你想要模拟当你尝试去睡觉时可能发生的烦恼。

模型在接口之前

在这里输入图像描述

 class Mosquito { void flyAroundYourHead(){} } class Neighbour{ void startScreaming(){} } class LampJustOutsideYourWindow(){ void shineJustThroughYourWindow() {} } 

正如你清楚地看到许多“东西”可能会让人烦恼,当你尝试睡觉。

没有接口的类的使用

但是当涉及到使用这些类时,我们有一个问题。 他们没有共同之处。 你必须分别调用每个方法。

 class TestAnnoyingThings{ void testAnnoyingThinks(Mosquito mosquito, Neighbour neighbour, LampJustOutsideYourWindow lamp){ if(mosquito != null){ mosquito.flyAroundYourHead(); } if(neighbour!= null){ neighbour.startScreaming(); } if(lamp!= null){ lamp.shineJustThroughYourWindow(); } } } 

模型与接口

为了克服这个问题,我们可以引入一个iterface 在这里输入图像描述

 interface Annoying{ public void annoy(); } 

并在类中实现它

 class Mosquito implements Annoying { void flyAroundYourHead(){} void annoy(){ flyAroundYourHead(); } } class Neighbour implements Annoying{ void startScreaming(){} void annoy(){ startScreaming(); } } class LampJustOutsideYourWindow implements Annoying{ void shineJustThroughYourWindow() {} void annoy(){ shineJustThroughYourWindow(); } } 

与接口一起使用

这将使这些类的使用更容易

 class TestAnnoyingThings{ void testAnnoyingThinks(Annoying annoying){ annoying.annoy(); } } 

扩展Larsenal所说的。 接口是所有实施类必须遵循的合同。 正因为如此,你可以使用一种叫做编程的技术来契约。 这使您的软件成为独立的实现。

当你想定义对象可以performance的行为时,通常使用接口。

在.NET世界中,一个很好的例子就是IDisposable接口,它用在任何使用必须手动发布的系统资源的Microsoft类上。 它要求实现它的类有一个Dispose()方法。

(Dispose()方法也被VB.NET和C#的使用语言结构调用,它只能在IDisposable

请记住,您可以检查一个对象是否通过使用TypeOf ... Is等结构实现特定的接口TypeOf ... Is (VB.NET), is (C#), instanceof (Java)等…

正如几个人可能已经回答的那样,可以使用接口来强化类之间的某些行为,而这些行为不会以相同的方式实现这些行为。 所以通过实现一个接口,你说你的类具有接口的行为。 因为Dog,Cat,Bird等类是动物的types,所以IAnimal接口不是一个典型的接口,应该扩展它,这是inheritance的一个例子。 相反,在这种情况下,接口将更像动物行为,例如IRunnable,IFlyable,ITrainable等。

接口对许多事情都有好处,关键的一点是可插拔性。 例如,声明具有List参数的方法将允许实现List接口的任何内容被传入,从而允许开发人员稍后移除并插入不同的列表,而不必重写大量的代码。

有可能你永远不会使用接口,但是如果你从头开始devise一个项目,特别是某种types的框架,你可能想要熟悉它们。

我build议阅读Coad,Mayfield和Kern在Java Design的接口一章。 他们解释说比一般的介绍性文字好一点。 如果你不使用Java,你可以阅读本章的开头部分,这只是概念。

由于任何增加系统灵活性的编程技术,接口也增加了一定程度的复杂性。 它们通常很棒,你可以在任何地方使用它(你可以为你的所有类创build一个接口),但是这样做会创build一个更难以维护的更复杂的系统。

像往常一样,这里有一个权衡:灵活性比可维护性。 哪一个更重要? 没有答案 – 这取决于项目。 但是请记住,每个软件都必须维护…

所以我的build议是:不要使用接口,直到你真的需要它们。 (使用Visual Studio,您可以在2秒内从现有类中提取界面 – 所以不要着急。)

话虽如此,你什么时候需要创build一个接口?

当我重构一个突然需要处理两个或更多类似类的方法时,我会这样做。 然后我创build一个接口,将该接口分配给两个(或更多)类似的类,并更改方法参数types(用接口typesreplace类types)。

它的工作原理:o)

有一个例外:当我嘲笑对象的时候,界面使用起来要容易得多。 所以我经常为此创build界面。

PS:当我写“接口”时,我的意思是:“任何基类的接口”,包括纯接口类。 请注意,抽象类通常是纯粹的接口更好的select,因为你可以添加逻辑。

问候,Sylvain。

当您成为图书馆开发人员 (编写其他编码人员的人)时,接口将变得明显。 我们大多数人都是以应用程序开发人员的身份开始的,我们使用现有的API和编程库

除了接口是合同外 ,没有人提到接口是使代码的某些部分稳定的好方法 。 当它是一个团队项目时(或者当您开发其他开发人员使用的代码时),这是特别有用的。 所以,下面是你的具体情况:

当您在团队中开发代码时,其他人可能会使用您编写的代码。 当他们编写你的(稳定的)接口时,他们会感到非常高兴,当你有自由改变你的实现(隐藏在接口之后)的时候,你会很高兴,而不会破坏你的团队的代码。 这是信息隐藏的一个变种(接口是公开的,实现对客户端程序员是隐藏的)。 阅读更多关于保护变化 。

另请参阅有关编码到接口的相关问题 。

考虑你正在做一个第一人称射击游戏。 玩家有多个枪可供select。

我们可以有一个界面Gun定义一个functionshoot()

我们需要Gun类的不同的子类即ShotGun Sniper等等。

 ShotGun implements Gun{ public void shoot(){ \\shotgun implementation of shoot. } } Sniper implements Gun{ public void shoot(){ \\sniper implementation of shoot. } } 

射击类

射手拥有所有的枪械。 让我们创build一个List来表示它。

 List<Gun> listOfGuns = new ArrayList<Gun>(); 

射手在枪中循环使用switchGun()函数,

 public void switchGun(){ //code to cycle through the guns from the list of guns. currentGun = //the next gun in the list. } 

我们可以使用上面的函数设置当前的枪,并调用fire()函数时,只需调用shoot()函数。

 public void fire(){ currentGun.shoot(); } 

拍摄function的行为将根据Gun接口的不同实施而有所不同。

结论

创build一个接口,当一个类函数依赖于来自另一个类的函数时,这个函数会根据实现的类的实例(对象)来改变它的行为。

例如Shooter类的fire()函数期望枪( SniperShotGun )执行shoot()函数。 所以如果我们切换枪和火。

 shooter.switchGun(); shooter.fire(); 

我们改变了fire()函数的行为。

使用接口有很多目的。

  1. 用于多态行为。 你想在什么地方调用具有引用子类的接口的子类的具体方法。

  2. 与类有一个契约来实现所有需要的方法,比如最常用的就是COM对象,在inheritance接口的DLL上生成一个包装类; 这些方法是在幕后调用的,你只需要实现它们,但是只有通过它们暴露的接口才能知道的COM DLL中定义的结构相同。

  3. 通过加载类中的特定方法来减less内存使用量。 就像如果您有三个业务对象并且它们在一个类中实现的一样,您可以使用三个接口。

例如IUser,IOrder,IOrderItem

 public interface IUser() { void AddUser(string name ,string fname); } // Same for IOrder and IOrderItem // public class BusinessLayer: IUser, IOrder, IOrderItem { public void AddUser(string name ,string fname) { // Do stuffs here. } // All methods from all interfaces must be implemented. } 

如果你只想添加一个用户,就这样做:

 IUser user = new (IUser)BusinessLayer(); // It will load all methods into memory which are declared in the IUser interface. user.AddUser();