Why can't I seem to grasp interfaces?
有人能帮我解开接口的神秘面纱,或者给我举几个好例子吗?我一直看到界面在这里和那里弹出,但我从未真正接触过界面的良好解释或何时使用它们。
我说的是接口和抽象类的上下文中的接口。
接口允许您根据"描述"而不是类型进行编程,这允许您更松散地关联软件的元素。
这样想:你想和你旁边的立方体中的某个人共享数据,所以你拿出你的flash-stick并复制/粘贴。你走到隔壁,那家伙说"那是USB吗?"你说是的-一切就绪。不管闪光灯的大小,也不管它的制造商——重要的是它的USB。
同样地,接口允许您将开发通用化。使用另一个类比-假设您想要创建一个应用程序,该应用程序实际上为汽车喷漆。您可能有这样的签名:
1 | public void Paint(Car car, System.Drawing.Color color)... |
这会起作用,直到你的客户说"现在我想给卡车喷漆",这样你才能做到:
1 | public void Paint (Vehicle vehicle, System.Drawing.Color color)... |
这会拓宽你的应用程序…直到你的客户说"现在我要粉刷房子!"从一开始就可以创建一个接口:
1 2 3 | public interface IPaintable{ void Paint(System.Drawing.Color color); } |
…然后把它传给你的日常生活:
1 2 3 | public void Paint(IPaintable item, System.Drawing.Color color){ item.Paint(color); } |
希望这是有意义的-这是一个相当简单的解释,但希望能深入到它的核心。
接口在类和调用它的代码之间建立契约。它们还允许您拥有实现相同接口但执行不同操作或事件的类似类,并且不必知道实际使用的是哪个类。作为一个例子,这可能更有意义,所以让我在这里尝试一下。
比如说你有几门课叫狗、猫和老鼠。这些类中的每一个都是宠物,理论上你可以从另一个称为宠物的类继承它们,但问题是。宠物本身什么都不做。你不能去商店买宠物。你可以去买狗或猫,但宠物是一个抽象的概念,而不是具体的。
所以你知道宠物可以做某些事情。它们可以睡觉、吃饭等,所以您定义了一个名为ipet的接口,它看起来像这样(C语法)
1 2 3 4 5 | public interface IPet { void Eat(object food); void Sleep(int duration); } |
您的每个狗、猫和鼠标类都实现IPET。
1 | public class Dog : IPet |
所以现在每个类都必须有自己的饮食和睡眠实现。你有合同…现在有什么意义。
接下来,假设您想创建一个名为petstore的新对象。这不是一个很好的宠物店,所以他们基本上只是卖给你一个随机的宠物(是的,我知道这是一个人为的例子)。
1 2 3 4 5 6 7 8 9 10 | public class PetStore { public static IPet GetRandomPet() { //Code to return a random Dog, Cat, or Mouse } } IPet myNewRandomPet = PetStore.GetRandomPet(); myNewRandomPet.Sleep(10); |
问题是你不知道它是什么类型的宠物。多亏了界面,尽管你知道它是什么,它会吃和睡。
因此,这个答案可能一点也没有帮助,但总的来说,接口可以让你做一些很好的事情,比如依赖注入和控制反转,在那里你可以得到一个对象,有一个定义良好的对象可以做的事情列表,而不必真正知道对象的具体类型。
最简单的答案是,接口定义了类可以做什么。这是一个"合同",表明你的班级将能够做到这一点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | Public Interface IRollOver Sub RollOver() End Interface Public Class Dog Implements IRollOver Public Sub RollOver() Implements IRollOver.RollOver Console.WriteLine("Rolling Over!") End Sub End Class Public Sub Main() Dim d as New Dog() Dim ro as IRollOver = TryCast(d, IRollOver) If ro isNot Nothing Then ro.RollOver() End If End Sub |
基本上,只要Dog类继续实现该接口,就可以保证它始终具有回滚的能力。如果猫获得了rollover()的能力,它们也可以实现该接口,并且当要求猫和狗进行rollover()时,您可以对它们进行均匀的治疗。
当你开朋友的车时,你或多或少知道怎么做。这是因为传统汽车都有一个非常相似的接口:方向盘、踏板等等。把这个接口看作是汽车制造商和司机之间的合同。作为一个司机(软件术语中界面的用户/客户),你不需要学习不同汽车的细节就能驾驶它们:例如,你只需要知道转动方向盘就能让汽车转动。作为一个汽车制造商(软件术语中接口实现的提供者),你清楚地知道你的新车应该拥有什么,以及它应该如何工作,这样司机就可以在不需要太多额外培训的情况下使用它们。这个契约就是软件设计中的人们所说的脱钩(用户与提供者之间的脱钩)——客户机代码是关于使用接口而不是接口的特定实现,因此不需要知道实现接口的对象的细节。
接口是一种减少系统不同部分(可能是不同部分)之间耦合的机制。好的。
从.NET的角度好的。
- 接口定义是操作和/或属性的列表。
- 接口方法始终是公共的。
- 接口本身不必是公共的。
创建实现接口的类时,必须提供接口定义的所有方法和属性的显式或隐式实现。好的。
此外,.NET只有一个继承,并且一个对象必须将方法公开给其他不知道的对象,或者不在其类层次结构之外的对象。这也被称为暴露行为。好的。一个更具体的例子:
考虑一下,我们有许多DTO(数据传输对象),它们都具有上次更新的对象以及更新时间的属性。问题是并非所有DTO都具有此属性,因为它并不总是相关的。好的。
同时,我们希望有一种通用机制来确保在提交到工作流时设置这些属性(如果可用),但是工作流对象应该与提交的对象松散耦合。也就是说,提交工作流方法不应该真正了解每个对象的所有细微之处,工作流中的所有对象不一定都是DTO对象。好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // First pass - not maintainable void SubmitToWorkflow(object o, User u) { if (o is StreetMap) { var map = (StreetMap)o; map.LastUpdated = DateTime.UtcNow; map.UpdatedByUser = u.UserID; } else if (o is Person) { var person = (Person)o; person.LastUpdated = DateTime.Now; // Whoops .. should be UtcNow person.UpdatedByUser = u.UserID; } // Whoa - very unmaintainable. |
在上面的代码中,
1 2 3 4 5 6 7 8 9 | // Second pass - brittle void SubmitToWorkflow(object o, User u) { if (o is DTOBase) { DTOBase dto = (DTOBase)o; dto.LastUpdated = DateTime.UtcNow; dto.UpdatedByUser = u.UserID; } |
它稍微好一点,但还是易碎的。如果我们想提交其他类型的对象,我们仍然需要更多的case语句。等。好的。
1 2 3 4 5 | // Third pass pass - also brittle void SubmitToWorkflow(DTOBase dto, User u) { dto.LastUpdated = DateTime.UtcNow; dto.UpdatedByUser = u.UserID; |
它仍然是脆弱的,并且两种方法都施加了约束,即所有DTO都必须实现我们指出的不普遍适用的这个属性。有些开发人员可能会尝试编写什么都不做的方法,但这很糟糕。我们不希望类假装支持更新跟踪,但不希望。好的。接口,它们如何帮助?
如果我们定义一个非常简单的接口:好的。
1 2 3 4 5 | public interface IUpdateTracked { DateTime LastUpdated { get; set; } int UpdatedByUser { get; set; } } |
任何需要此自动更新跟踪的类都可以实现该接口。好的。
1 2 3 4 | public class SomeDTO : IUpdateTracked { // IUpdateTracked implementation as well as other methods for SomeDTO } |
工作流方法可以变得更通用、更小和更易于维护,并且无论有多少类实现接口(DTO或其他),它都将继续工作,因为它只处理接口。好的。
1 2 3 4 5 6 7 8 9 | void SubmitToWorkflow(object o, User u) { IUpdateTracked updateTracked = o as IUpdateTracked; if (updateTracked != null) { updateTracked.LastUpdated = DateTime.UtcNow; updateTracked.UpdatedByUser = u.UserID; } // ... |
- 我们可以注意到,
void SubmitToWorkflow(IUpdateTracked updateTracked, User u) 的变化可以保证类型安全,但在这些情况下,它似乎并不相关。
在我们使用的一些生产代码中,我们有代码生成来从数据库定义创建这些DTO类。开发人员唯一要做的就是正确地创建字段名并用接口修饰类。只要属性被称为lastupdated和updatedbyuser,它就可以工作。好的。
也许你在问,如果我的数据库是遗留的,而这是不可能的,会发生什么?你只需要做更多的输入;接口的另一个重要特性是它们可以让你在类之间建立一个桥梁。好的。
在下面的代码中,我们有一个虚构的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // Using an interface to bridge properties public class LegacyDTO : IUpdateTracked { public int LegacyUserID { get; set; } public DateTime LastSaved { get; set; } public int UpdatedByUser { get { return LegacyUserID; } set { LegacyUserID = value; } } public DateTime LastUpdated { get { return LastSaved; } set { LastSaved = value; } } } |
你可能会觉得很酷,但拥有多个属性会让人困惑吗?或者,如果已经有了这些属性,但它们意味着其他东西,会发生什么?.NET使您能够显式实现接口。好的。
这意味着只有当我们使用一个对IUPDactocked的引用时,IUPDactocked属性才会可见。注意声明中没有公共修饰符,声明中包含接口名。好的。
1 2 3 4 5 6 7 | // Explicit implementation of an interface public class YetAnotherObject : IUpdatable { int IUpdatable.UpdatedByUser { ... } DateTime IUpdatable.LastUpdated { ... } |
具有如此大的灵活性来定义类如何实现接口,使开发人员可以自由地将对象与使用它的方法分离。接口是一种很好的打破耦合的方法。好的。
接口不仅仅是这个。这只是一个简化的现实例子,它利用了基于接口编程的一个方面。好的。
正如我前面提到的,以及其他响应程序,您可以创建获取和/或返回接口引用的方法,而不是特定的类引用。如果需要在列表中查找重复项,我可以编写一个方法,该方法接受并返回一个
1 2 3 4 5 6 7 8 9 | // Decouples the caller and the code as both // operate only on IList, and are free to swap // out the concrete collection. public IList<T> FindDuplicates( IList<T> list ) { var duplicates = new List<T>() // TODO - write some code to detect duplicate items return duplicates; } |
版本控制警告
如果它是一个公共接口,那么您将声明i-guaranterface x如下所示!一旦您发布了代码并发布了接口,就不应该更改它。一旦使用者代码开始依赖该接口,就不希望在字段中破坏它们的代码。好的。
看这篇haacked的文章,好好讨论一下。好的。接口与抽象(基本)类
抽象类可以提供实现,而接口不能。如果您遵循一些指导原则(如nvpi(非虚拟公共接口)模式),那么抽象类在版本控制方面在某些方面会更加灵活。好的。
值得重申的是,在.NET中,类只能从单个类继承,但类可以实现任意多的接口。好的。依赖注入
接口和依赖注入(DI)的快速总结是,使用接口使开发人员能够编写针对接口编程的代码来提供服务。在实践中,您可以得到许多小的接口和小的类,一个想法是,只做一件事的小类更容易编码和维护。好的。
1 2 3 4 5 6 7 8 9 10 11 | class AnnualRaiseAdjuster : ISalaryAdjuster { AnnualRaiseAdjuster(IPayGradeDetermination payGradeDetermination) { ... } void AdjustSalary(Staff s) { var payGrade = payGradeDetermination.Determine(s); s.Salary = s.Salary * 1.01 + payGrade.Bonus; } } |
简而言之,上面的代码片段中显示的好处是,工资等级的确定只是注入到年度加薪调整器中。如何确定工资等级对这门课实际上并不重要。在测试时,开发人员可以模拟薪级确定结果,以确保薪资调整器按需工作。测试也很快,因为测试只测试类,而不是其他所有的类。好的。
这不是一个DI初级读物,尽管有很多关于这个主题的书;上面的例子非常简单。好的。好啊。
这是一个相当"长"的主题,但让我试着简单点。
一个接口是-作为"他们命名它"-一个合同。但忘了那个词。
理解它们的最好方法是通过某种伪代码示例。这就是我很久以前理解它们的方式。
假设您有一个处理消息的应用程序。消息包含一些内容,如主题、文本等。
因此,您可以编写messagecontroller来读取数据库,并提取消息。很高兴,直到你突然听到传真也将很快实施。因此,您现在必须阅读"传真"并将其作为消息处理!
这很容易变成一个Spagetti代码。因此,您所做的不是使用messagecontroller,而是只控制"messages",而是使用一个名为IMessage的接口(i只是常见用法,但不是必需的)。
IMessage接口包含一些基本数据,您需要确保能够处理消息。
因此,当您创建电子邮件、传真、电话呼叫类时,您会让它们实现名为IMessage的接口。
因此,在messagecontroller中,可以有一个这样的方法:
1 2 3 4 | private void ProcessMessage(IMessage oneMessage) { DoSomething(); } |
如果您没有使用接口,那么您必须:
1 2 3 | private void ProcessEmail(Email someEmail); private void ProcessFax(Fax someFax); etc. |
因此,通过使用一个公共接口,您只需确保processMessage方法能够使用它,无论它是传真、电子邮件、电话呼叫等。
为什么?
因为接口是一个约定,它指定了一些必须遵循(或实现)才能使用它的东西。把它当作一个徽章。如果您的对象"fax"没有IMessage接口,那么您的processMessage方法将无法使用它,它将为您提供一个无效的类型,因为您正在将传真传递给需要IMessage对象的方法。
你明白这一点吗?
把接口看作是方法和属性的"子集",尽管它是真实的对象类型。如果原始对象(传真、电子邮件、电话呼叫等)实现了该接口,则可以在需要该接口的方法之间安全地传递它。
还有更多的魔法隐藏在里面,你可以将接口投射回它们的原始对象:
传真myfax=(fax)somememessagethatireceive;
.NET 1.1中的arraylist()有一个名为ilist的好接口。如果您有一个IList(非常"通用"),您可以将其转换为数组列表:
1 | ArrayList ar = (ArrayList)SomeIList; |
在野外有成千上万的样本。
接口(如Isortable、IComparable等)定义必须在类中实现的方法和属性,以便实现该功能。
要扩展我们的示例,如果类型为IMessage,则可以将电子邮件、传真、电话呼叫的列表<>放在同一列表中,但如果对象只是电子邮件、传真等,则不能将它们放在一起。
如果要对对象进行排序(或枚举),则需要它们来实现相应的接口。在.NET示例中,如果您有一个"fax"对象列表,并且希望能够使用mylist.sort()对其进行排序,则需要使您的fax类如下所示:
1 2 3 4 | public class Fax : ISorteable { //implement the ISorteable stuff here. } |
我希望这能给你一个提示。其他用户可能会发布其他好的例子。祝你好运!拥抱界面的力量。
警告:并不是所有的接口都是好的,它们有一些问题,OOP纯粹主义者将对此发动战争。我会留在一边。interce的一个缺点(至少在.NET 2.0中)是您不能拥有私有成员,也不能保护私有成员,它必须是公共的。这是有道理的,但有时你希望你能简单地声明这些东西是私有的或受保护的。
除了编程语言中的函数接口之外,在向其他人表达设计思想时,它们也是一个强大的语义工具。
具有设计良好的接口的代码库突然变得更容易讨论。"是的,您需要一个凭证管理器来注册新的远程服务器。"将属性映射传递到ThingFactory以获取工作实例。"
用一个词来处理复杂事物的能力非常有用。
接口允许您以通用的方式对对象进行编码。例如,假设您有一个发送报告的方法。现在假设你有一个新的要求,你需要写一个新的报告。如果您可以重用已经编写好的方法,那就更好了?接口使这变得容易:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | interface IReport { string RenderReport(); } class MyNewReport : IReport { public string RenderReport() { return"Hello World Report!"; } } class AnotherReport : IReport { public string RenderReport() { return"Another Report!"; } } //This class can process any report that implements IReport! class ReportEmailer() { public void EmailReport(IReport report) { Email(report.RenderReport()); } } class MyApp() { void Main() { //create specific"MyNewReport" report using interface IReport newReport = new MyNewReport(); //create specific"AnotherReport" report using interface IReport anotherReport = new AnotherReport(); ReportEmailer reportEmailer = new ReportEmailer(); //emailer expects interface reportEmailer.EmailReport(newReport); reportEmailer.EmailReport(anotherReport); } } |
界面也是多态性的关键,多态性是"ood的三大支柱"之一。
有人在上面提到过,多态性仅仅意味着一个给定的类可以有不同的"形式"。意思是,如果我们有两个类,"dog"和"cat",并且都实现了接口"inedFreshFoodandWater"(hehe),那么您的代码可以这样做(伪代码):
1 2 3 4 5 6 7 8 9 | INeedFreshFoodAndWater[] array = new INeedFreshFoodAndWater[]; array.Add(new Dog()); array.Add(new Cat()); foreach(INeedFreshFoodAndWater item in array) { item.Feed(); item.Water(); } |
这是非常强大的,因为它允许您抽象地处理不同类别的对象,并允许您做一些事情,比如使对象更松散地耦合,等等。
好吧,这是关于抽象类和接口的…
从概念上讲,抽象类可以用作基类。通常情况下,它们本身已经提供了一些基本功能,子类必须提供自己的抽象方法实现(这些方法不是在抽象基类中实现的)。
接口主要用于将客户机代码与特定实现的细节分离。此外,有时在不更改客户机代码的情况下切换实现的能力会使客户机代码更加通用。
在技术层面上,抽象类和接口之间很难划清界限,因为在某些语言(例如,C++)中,没有句法差异,或者因为还可以使用抽象类来实现解耦或泛化。使用抽象类作为接口是可能的,因为根据定义,每个基类都定义了一个其所有子类都应遵守的接口(即,应该可以使用子类而不是基类)。
您遇到的大多数接口都是方法和属性签名的集合。任何实现接口的人都必须为接口中的内容提供定义。
接口是一种强制对象实现一定数量功能的方法,而不必使用继承(这会导致强耦合代码,而不是通过使用接口实现的松散耦合代码)。
接口描述的是功能,而不是实现。
下面是我经常使用的一个与数据库相关的示例。假设您有一个对象和一个像列表一样的容器对象。让我们假设,有时您可能希望以特定的顺序存储对象。假设序列与数组中的位置无关,而是与一组较大对象的子集相关,序列位置与数据库SQL筛选相关。
为了跟踪定制的序列位置,可以使对象实现定制接口。自定义接口可以调解维护此类序列所需的组织工作。
例如,您感兴趣的序列与记录中的主键无关。对于实现接口的对象,可以说myObject.next()或myObject.prev()。
理解接口的最简单方法是从考虑类继承的含义开始。它包括两个方面:
这两个特性都是有用的,但是由于很难允许一个类使用多个类的成员作为它自己的成员,许多语言和框架只允许从一个基类继承类。另一方面,让一个类可替换为多个其他无关的东西并没有特别的困难。
此外,由于继承的第一个好处可以通过封装实现,因此允许第一个类型的多重继承的相对好处是有限的。另一方面,能够将一个对象替换为多种不相关类型的事物是一种有用的能力,如果没有语言支持,这是不容易实现的。
接口提供了一种方法,通过这种方法,语言/框架可以让程序从多个基类型继承的第二个方面获益,而不需要它也提供第一个方面。
简单地说:接口是一个定义了方法但没有实现的类。相反,抽象类实现了一些方法,但不是全部实现。
我和你有同样的问题,我觉得"合同"解释有点混乱。
如果指定某个方法将IEnumerable接口作为In参数,则可以说这是一个约定,指定该参数必须是继承自IEnumerable接口的类型,因此支持IEnumerable接口中指定的所有方法。但如果我们使用抽象类或普通类,情况也是如此。从这些类继承的任何对象都可以作为参数传入。在任何情况下,您都可以说继承的对象支持基类中的所有公共方法,不管基类是普通类、抽象类还是接口。
具有所有抽象方法的抽象类基本上与接口相同,因此可以说,接口只是一个没有实现方法的类。实际上,您可以从语言中删除接口,而只使用抽象类和抽象方法。我认为我们分开它们的原因是出于语义上的原因,但是出于编码上的原因,我看不出原因,发现它只是令人困惑。
另一个建议是将接口重命名为接口类,因为接口只是类的另一个变体。
在某些语言中,有一些细微的差异,允许一个类只继承一个类,但继承多个接口,而在其他语言中,两者都可以,但这是另一个问题,我认为这不是直接相关的。
接口是一种实现约定的方法,它仍然具有强类型和多态性。
一个好的现实世界例子是.NET中的IDisposable。实现IDisposable接口的类强制该类实现Dispose()方法。如果类不实现Dispose(),则在尝试生成时会出现编译器错误。此外,此代码模式:
1 2 3 4 | using (DisposableClass myClass = new DisposableClass()) { // code goes here } |
将导致在执行退出内部块时自动执行MyClass.Dispose()。
但是,这一点很重要,对于Dispose()方法应该做什么没有强制执行。您可以让dispose()方法从一个文件中选择随机的配方,然后通过电子邮件将它们发送到一个分发列表,编译器不在乎。IDisposable模式的目的是使清理资源更加容易。如果类的实例将保留在文件句柄上,那么IDisposable可以很容易地将释放和清理代码集中在一个位置,并促进一种确保释放总是发生的使用风格。
这就是接口的关键。它们是一种简化编程约定和设计模式的方法。当正确使用时,它会促进更简单、自我记录的代码,这些代码更易于使用、更易于维护和更正确。
简单答案:一个接口是一组方法签名(+返回类型)。当一个对象说它实现了一个接口时,您知道它公开了这组方法。
正如其他人在这里所说,接口定义了一个契约(使用接口的类将如何"看起来"),抽象类定义了共享功能。
让我们看看代码是否有帮助:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public interface IReport { void RenderReport(); // This just defines the method prototype } public abstract class Reporter { protected void DoSomething() { // This method is the same for every class that inherits from this class } } public class ReportViolators : Reporter, IReport { public void RenderReport() { // Some kind of implementation specific to this class } } public class ClientApp { var violatorsReport = new ReportViolators(); // The interface method violatorsReport.RenderReport(); // The abstract class method violatorsReport.DoSomething(); } |
假设您引用的是静态类型的面向对象语言中的接口,那么主要的用途是断言类遵循特定的契约或协议。
说你有:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public interface ICommand { void Execute(); } public class PrintSomething : ICommand { OutputStream Stream { get; set; } String Content {get; set;} void Execute() { Stream.Write(content); } } |
现在您有了一个可替换的命令结构。实现IExecute的类的任何实例都可以存储在某种类型的列表中,比如说实现IEnumerable的一些实例,并且您可以循环并执行每个实例,因为您知道每个对象都会做正确的事情。您可以通过实现compositecommand来创建一个复合命令,compositecommand将有自己的命令列表来运行,或者通过循环命令来重复运行一组命令,这样就可以使用大多数简单的解释器。
当您可以将一组对象减少到它们共同拥有的行为时,您可能有理由提取接口。此外,有时您可以使用接口来防止对象意外地侵入该类的关注点;例如,您可以实现一个只允许客户端检索的接口,而不是更改对象中的数据,并且让大多数对象只接收对检索接口的引用。
当您的接口相对简单并且很少做假设时,接口工作得最好。
查一下里斯科夫的子交换原则,以便更好地理解这一点。
一些静态类型的语言,如C++,不支持接口作为一个一流的概念,所以使用纯抽象类创建接口。
更新既然您似乎在问抽象类和接口,下面是我最喜欢的过于简单化:
- 接口定义功能和特性。
- 抽象类定义核心功能。
通常,我会在构建抽象类之前进行提取接口重构。如果我认为应该有一个创造性的契约(具体来说,一个特定类型的构造函数应该总是由子类支持),那么我更有可能构建一个抽象类。但是,我很少使用C语言中的"纯"抽象类。我更可能使用至少一个包含有意义行为的方法来实现类,并使用抽象方法来支持该方法调用的模板方法。抽象类是行为的基本实现,所有具体的子类都可以利用它而不必重新实现。
Java不允许多重继承(出于非常好的理由,查找可怕的菱形),但是如果您想让您的类提供多组行为,该怎么办?假设您希望任何使用它的人都知道它可以被序列化,并且它可以在屏幕上绘制自己。答案是实现两个不同的接口。
因为接口不包含自己的实现,也不包含实例成员,所以在同一个类中实现其中的几个成员是安全的,没有歧义。
缺点是,您必须在每个类中分别拥有实现。因此,如果您的层次结构很简单,并且实现的某些部分对于所有继承类都应该是相同的,那么就使用一个抽象类。
接口就像一个完全抽象的类。也就是说,只有抽象成员的抽象类。您还可以实现多个接口,就像从多个完全抽象的类继承一样。反正…只有理解抽象类是什么,这个解释才有帮助。
在Java中使用接口与抽象类的一个很好的理由是,子类不能扩展多个基类,但它可以实现多个接口。
把一个接口看作一个契约。当一个类实现一个接口时,它实质上同意遵守该契约的条款。作为一个消费者,你只关心你拥有的对象是否能够履行他们的合同义务。它们的内部工作和细节并不重要。
接口需要实现它们的任何类来包含接口中定义的方法。
这样做的目的是,在不必看到类中的代码的情况下,您可以知道它是否可以用于某个任务。例如,Java中的整型类实现了可比较的接口,因此,如果只看到方法头(公共类字符串实现可比性),您就会知道它包含一个CuasReto()方法。
在简单的情况下,您可以通过使用实现
1 2 | Bird bird = GetMeAnInstanceOfABird(someCriteriaForSelectingASpecificKindOfBird); bird.Fly(Direction.South, Speed.CruisingSpeed); |
这样你就可以编写代码来处理任何鸟的事情。然后,您可以编写代码,使鸟做它的事情(飞,吃,产卵,等等),在它视为鸟的实例上进行操作。无论这只鸟是真的鹰、鹰,还是其他任何从鸟身上衍生出来的东西,这一代码都是有效的。
然而,当你没有一个真实的关系时,这种模式开始变得混乱。比如说,你想写代码,让事情在空中飞来飞去。如果您编写代码来接受bird基类,那么在一个jumbojet实例上使用该代码会突然变得困难,因为虽然bird和jumbojet都可以飞行,但jumbojet肯定不是bird。
进入界面。
鸟(鹰和鹰)的共同点是它们都能飞。如果您编写上述代码而不是在接口(iFly)上执行,则该代码可以应用于任何为该接口提供实现的内容。