有人可以为我揭开界面的神秘面纱或指出一些好的例子吗?我不断地看到接口弹出来这里和那里,但我从来没有真正接触过关于接口或何时使用它们的良好解释。
我在接口与抽象类的上下文中谈论接口。
接口允许您根据“描述”而不是类型进行编程,这允许您更松散地关联软件的元素。
可以这样想:您想与您旁边的立方体中的某人共享数据,因此您拔出闪存棒并复制/粘贴。你走到隔壁,那个人说“那是USB吗?” 你说是的 - 一切就绪。闪存棒的大小和制造商无关紧要 - 重要的是它是 USB。
以同样的方式,接口允许您对您的开发进行通用化。使用另一个类比 - 假设您想创建一个虚拟绘制汽车的应用程序。你可能有这样的签名:
public void Paint(Car car, System.Drawing.Color color)...
这将一直有效,直到您的客户说“现在我想画卡车”,所以您可以这样做:
public void Paint (Vehicle vehicle, System.Drawing.Color color)...
这将扩大您的应用程序......直到您的客户说“现在我想画房子!” 您可以从一开始就创建一个界面:
public interface IPaintable{
void Paint(System.Drawing.Color color);
}
...并将其传递给您的例程:
public void Paint(IPaintable item, System.Drawing.Color color){
item.Paint(color);
}
希望这是有道理的——这是一个非常简单的解释,但希望能深入人心。
接口在类和调用它的代码之间建立了契约。它们还允许您拥有实现相同接口但执行不同操作或事件的类似类,而不必知道您实际使用的是哪个类。作为一个例子,这可能更有意义,所以让我在这里尝试一个。
假设您有几个名为 Dog、Cat 和 Mouse 的类。这些类中的每一个都是一个 Pet,理论上你可以从另一个名为 Pet 的类中继承它们,但问题就在这里。宠物本身不做任何事情。你不能去商店买宠物。你可以去买一只狗或一只猫,但宠物是一个抽象的概念,而不是具体的。
所以你知道宠物可以做某些事情。他们可以睡觉或吃饭等。所以你定义了一个名为 IPet 的接口,它看起来像这样(C# 语法)
public interface IPet
{
void Eat(object food);
void Sleep(int duration);
}
您的每个 Dog、Cat 和 Mouse 类都实现了 IPe。
public class Dog : IPet
所以现在每个类都必须有自己的 Eat 和 Sleep 实现。是的,你有合同......现在有什么意义。
接下来假设您要创建一个名为 PetStore 的新对象。这不是一个很好的 PetStore,所以他们基本上只是向您出售随机宠物(是的,我知道这是一个人为的例子)。
public class PetStore
{
public static IPet GetRandomPet()
{
//Code to return a random Dog, Cat, or Mouse
}
}
IPet myNewRandomPet = PetStore.GetRandomPet();
myNewRandomPet.Sleep(10);
问题是你不知道它会是什么类型的宠物。多亏了界面,尽管您知道它会吃什么和睡什么。
所以这个答案可能根本没有帮助,但一般的想法是接口可以让你做一些简洁的事情,比如依赖注入和控制反转,你可以在其中获取一个对象,有一个明确定义的对象可以做的事情列表真的没有知道该对象的具体类型是什么。
最简单的答案是接口定义了你的类可以做什么。这是一份“合同”,规定您的班级将能够执行该操作。
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() 的能力,它们也可以实现该接口,并且当要求 Dogs 和 Cats 进行 RollOver() 时,您可以同质对待它们。
当你开朋友的车时,你或多或少知道该怎么做。这是因为传统汽车都有一个非常相似的界面:方向盘、踏板等等。将此接口视为汽车制造商和驾驶员之间的合同。作为驾驶员(软件术语中界面的用户/客户端),您无需了解不同汽车的详细信息即可驾驶它们:例如,您只需要知道转动方向盘会使汽车转弯。作为汽车制造商(软件方面接口实现的提供者),您清楚地知道您的新车应该具备什么以及它应该如何表现,以便驾驶员可以在没有太多额外培训的情况下使用它们。
接口是一种减少系统不同、可能不同部分之间耦合的机制。
从.NET的角度来看
创建实现接口的类时,必须提供接口定义的所有方法和属性的显式或隐式实现。
此外,.NET 只有单一继承,并且接口是对象向其他不知道或位于其类层次结构之外的对象公开方法的必要条件。这也称为暴露行为。
考虑一下我们有许多 DTO(数据传输对象),它们具有最后更新的人员以及更新时间的属性。问题是并非所有 DTO 都具有此属性,因为它并不总是相关的。
同时,我们需要一种通用机制来保证这些属性在提交到工作流时设置(如果可用),但工作流对象应该与提交的对象松散耦合。即提交工作流方法不应该真正了解每个对象的所有细节,并且工作流中的所有对象不一定都是DTO 对象。
// 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.
在上面的代码中,SubmitToWorkflow()
必须了解每个对象。此外,代码中包含大量 if/else/switch,违反了不重复(DRY) 原则,并且要求开发人员在每次将新对象添加到系统时记住复制/粘贴更改。
// 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语句。等等
// Third pass pass - also brittle
void SubmitToWorkflow(DTOBase dto, User u)
{
dto.LastUpdated = DateTime.UtcNow;
dto.UpdatedByUser = u.UserID;
它仍然很脆弱,并且这两种方法都施加了约束,即所有 DTO 都必须实现这个我们指出的属性并不普遍适用。一些开发人员可能很想编写什么都不做的方法,但这闻起来很糟糕。我们不希望类假装他们支持更新跟踪但不支持。
如果我们定义一个非常简单的接口:
public interface IUpdateTracked
{
DateTime LastUpdated { get; set; }
int UpdatedByUser { get; set; }
}
任何需要这种自动更新跟踪的类都可以实现该接口。
public class SomeDTO : IUpdateTracked
{
// IUpdateTracked implementation as well as other methods for SomeDTO
}
工作流方法可以变得更通用、更小、更易于维护,并且无论有多少类实现接口(DTO 或其他),它都将继续工作,因为它只处理接口。
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,它就可以工作。
也许您在问如果我的数据库是遗留数据库并且这是不可能的会发生什么?你只需要多打字一点;接口的另一个重要特性是它们可以让您在类之间创建一座桥梁。
在下面的代码中,我们有一个虚构的LegacyDTO
,一个具有类似名称字段的预先存在的对象。它正在实现 IUpdateTracked 接口以桥接现有但名称不同的属性。
// 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 使您能够显式实现接口。
这意味着 IUpdateTracked 属性仅在我们使用对 IUpdateTracked 的引用时才可见。请注意声明中没有 public 修饰符,并且声明包含接口名称。
// Explicit implementation of an interface
public class YetAnotherObject : IUpdatable
{
int IUpdatable.UpdatedByUser
{ ... }
DateTime IUpdatable.LastUpdated
{ ... }
拥有如此多的灵活性来定义类如何实现接口为开发人员提供了很大的自由来将对象与使用它的方法分离。接口是打破耦合的好方法。
接口不仅仅是这个。这只是一个简化的现实生活示例,它利用了基于接口的编程的一个方面。
正如我之前提到的,以及其他响应者,您可以创建获取和/或返回接口引用而不是特定类引用的方法。如果我需要在列表中查找重复项,我可以编写一个方法,该方法接受并返回一个IList
(定义对列表工作的操作的接口),并且我不限于具体的集合类。
// 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;
}
如果它是一个公共接口,你声明我保证接口 x 看起来像这样!一旦你发布了代码并发布了界面,你就永远不应该改变它。一旦消费者代码开始依赖该接口,您就不想在现场破坏他们的代码。
请参阅此 Haacked 帖子以进行很好的讨论。
抽象类可以提供实现,而接口不能。如果您遵循一些准则,例如 NVPI(非虚拟公共接口)模式,抽象类在某些方面在版本控制方面更加灵活。
值得重申的是,在 .NET 中,一个类只能从一个类继承,但一个类可以实现任意多个接口。
接口和依赖注入 (DI) 的快速总结是,接口的使用使开发人员能够编写针对接口编程的代码以提供服务。在实践中,您最终会得到很多小接口和小类,一种想法是,只做一件事且只做一件事的小类更容易编码和维护。
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 而不是仅控制“消息”,而是使其能够与名为 IMessage 的接口一起使用(I 只是常见用法,但不是必需的)。
您的 IMessage 界面包含一些您需要的基本数据,以确保您能够处理 Message 本身。
因此,当您创建 EMail、Fax、PhoneCall 类时,您让它们实现名为IMessage的接口。
所以在你的 MessageController 中,你可以有一个这样的方法:
private void ProcessMessage(IMessage oneMessage)
{
DoSomething();
}
如果您没有使用过接口,则必须:
private void ProcessEmail(Email someEmail);
private void ProcessFax(Fax someFax);
etc.
因此,通过使用通用接口,您只需确保 ProcessMessage 方法能够使用它,无论它是传真、电子邮件还是电话呼叫等。
为什么或如何?
因为接口是一个契约,它指定了一些你必须遵守(或实现)才能使用它的东西。把它想象成一个徽章。如果您的对象“Fax”没有 IMessage 接口,那么您的 ProcessMessage 方法将无法使用它,它会给您一个无效的类型,因为您将 Fax 传递给一个需要 IMessage 的方法目的。
你明白重点了吗?
将接口视为您将可用的方法和属性的“子集”,尽管是真实的对象类型。如果原始对象(Fax、Email、PhoneCall 等)实现了该接口,您可以安全地将其传递给需要该接口的方法。
那里隐藏着更多的魔法,你可以将接口转换回它们的原始对象:
传真 myFax = (Fax)SomeIMessageThatIReceive;
.NET 1.1 中的 ArrayList() 有一个很好的接口,称为 IList。如果你有一个 IList(非常“通用”),你可以将它转换成一个 ArrayList:
ArrayList ar = (ArrayList)SomeIList;
野外有数千个样本。
ISortable、IComparable 等接口定义了您必须在类中实现的方法和属性,以实现该功能。
为了扩展我们的示例,如果类型是 IMessage,您可以在同一个列表中拥有一个包含电子邮件、传真、电话呼叫的 List<>,但如果对象只是电子邮件、传真等,则不能将它们全部放在一起.
如果您想对对象进行排序(例如枚举),则需要它们来实现相应的接口。在 .NET 示例中,如果您有一个“传真”对象列表,并且希望能够使用 MyList.Sort() 对它们进行排序,则需要使您的传真类如下所示:
public class Fax : ISorteable
{
//implement the ISorteable stuff here.
}
我希望这能给你一个提示。其他用户可能会发布其他好的示例。祝你好运!拥抱接口的力量。
警告:接口并非一切都好,它们存在一些问题,OOP 纯粹主义者会对此展开战争。我将留在一边。Interfce 的一个缺点(至少在 .NET 2.0 中)是您不能拥有 PRIVATE 成员或受保护的成员,它必须是公共的。这是有道理的,但有时您希望您可以简单地将内容声明为私有或受保护。
除了编程语言中的功能接口外,它们也是向他人表达设计思想时的强大语义工具。
具有良好设计接口的代码库突然变得更容易讨论。“是的,您需要一个 CredentialsManager 来注册新的远程服务器。” “将 PropertyMap 传递给 ThingFactory 以获取工作实例。”
用一个词解决复杂事物的能力非常有用。
接口允许您以通用方式对对象进行编码。例如,假设您有一个发送报告的方法。现在假设您有一个新要求,您需要编写新报告。如果您可以重用您已经编写的方法,那会很好吗?接口使这很容易:
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”并且都实现了接口“INeedFreshFoodAndWater”(呵呵)——你的代码可以做这样的事情(伪代码):
INeedFreshFoodAndWater[] array = new INeedFreshFoodAndWater[];
array.Add(new Dog());
array.Add(new Cat());
foreach(INeedFreshFoodAndWater item in array)
{
item.Feed();
item.Water();
}
这很强大,因为它允许您抽象地处理不同类别的对象,并允许您做一些事情,例如使您的对象更松散耦合等。
好的,所以它是关于抽象类与接口的......
从概念上讲,抽象类被用作基类。很多时候,它们自己已经提供了一些基本功能,而子类必须提供它们自己的抽象方法实现(那些是在抽象基类中没有实现的方法)。
接口主要用于将客户端代码与特定实现的细节分离。此外,有时在不更改客户端代码的情况下切换实现的能力使客户端代码更加通用。
在技术层面上,很难在抽象类和接口之间划清界限,因为在某些语言(例如,C++)中,没有语法差异,或者因为您也可以将抽象类用于解耦或泛化的目的。使用抽象类作为接口是可能的,因为根据定义,每个基类都定义了一个其所有子类都应遵循的接口(即,应该可以使用子类而不是基类)。
接口是一种强制对象实现一定数量功能的方法,而不必使用继承(这导致代码强耦合,而不是松散耦合,可以通过使用接口来实现)。
接口描述的是功能,而不是实现。
Most of the interfaces you come across are a collection of method and property signatures. Any one who implements an interface must provide definitions to what ever is in the interface.
简单地说:接口是一个定义了方法但没有实现的类。相反,抽象类实现了一些方法,但不是全部。
将接口视为合同。当一个类实现一个接口时,它本质上是同意遵守该合同的条款。作为消费者,您只关心您拥有的物品能否履行其合同义务。他们的内部运作和细节并不重要。
这是我经常使用的一个与数据库相关的示例。假设您有一个对象和一个容器对象,例如列表。让我们假设有时您可能希望以特定顺序存储对象。假设序列与数组中的位置无关,而是对象是更大对象集的子集,并且序列位置与数据库 sql 过滤有关。
要跟踪您的自定义序列位置,您可以使您的对象实现自定义接口。自定义界面可以调解维护此类序列所需的组织工作。
例如,您感兴趣的序列与记录中的主键无关。对于实现接口的对象,您可以说 myObject.next() 或 myObject.prev()。
假设您指的是静态类型的面向对象语言中的接口,主要用途是断言您的类遵循特定的合同或协议。
假设你有:
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 创建一个复合命令,该命令将具有自己的要运行的命令列表,或者一个 LoopingCommand 重复运行一组命令,然后您将拥有大部分简单的解释器。
当您可以将一组对象简化为它们都具有的共同行为时,您可能有理由提取接口。此外,有时您可以使用接口来防止对象意外侵入该类的关注点;例如,您可以实现一个只允许客户端检索而不是更改对象中的数据的接口,并且让大多数对象只接收对检索接口的引用。
当您的界面相对简单并且很少做假设时,界面效果最好。
查阅 Liskov 替代原则以更清楚地理解这一点。
一些像 C++ 这样的静态类型语言不支持将接口作为一等概念,因此您使用纯抽象类创建接口。
更新 由于您似乎在询问抽象类与接口,这是我首选的过度简化:
通常,我会在构建抽象类之前进行提取接口重构。如果我认为应该有一个创建契约(特别是子类应该始终支持特定类型的构造函数),我更有可能构建一个抽象类。但是,我很少在 C#/java 中使用“纯”抽象类。我更有可能用至少一个包含有意义行为的方法来实现一个类,并使用抽象方法来支持该方法调用的模板方法。那么抽象类是一种行为的基本实现,所有具体的子类都可以利用它而无需重新实现。
简单的回答:接口是一堆方法签名(+返回类型)。当一个对象说它实现了一个接口时,你就知道它公开了那组方法。
在 Java 中使用接口与抽象类的一个很好的理由是子类不能扩展多个基类,但它可以实现多个接口。
Java 不允许多重继承(有很好的理由,请查看可怕的菱形)但是如果你想让你的类提供多组行为怎么办?假设您希望任何使用它的人都知道它可以被序列化,并且它可以在屏幕上绘制自己。答案是实现两个不同的接口。
因为接口不包含它们自己的实现,也不包含实例成员,所以在同一个类中实现它们中的几个是安全的,没有歧义。
不利的一面是,您必须分别在每个类中实现。因此,如果您的层次结构很简单,并且所有继承类的实现部分应该相同,请使用抽象类。
就像其他人在这里所说的那样,接口定义了一个契约(使用接口的类将如何“看起来”),抽象类定义共享功能。
让我们看看代码是否有帮助:
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();
}
接口是一种以仍然是强类型和多态的方式实现约定的方法。
一个很好的现实世界示例是 .NET 中的 IDisposable。实现 IDisposable 接口的类强制该类实现 Dispose() 方法。如果该类未实现 Dispose(),则在尝试构建时会出现编译器错误。此外,此代码模式:
using (DisposableClass myClass = new DisposableClass())
{
// code goes here
}
将导致 myClass.Dispose() 在执行退出内部块时自动执行。
但是,这很重要,对于您的 Dispose() 方法应该做什么没有强制执行。您可以让您的 Dispose() 方法从文件中选择随机配方并将它们通过电子邮件发送到分发列表,编译器不在乎。IDisposable 模式的目的是使清理资源更容易。如果一个类的实例将保留文件句柄,那么 IDisposable 可以很容易地将释放和清理代码集中在一个地方,并促进一种确保释放总是发生的使用风格。
这就是接口的关键。它们是一种简化编程约定和设计模式的方法。如果使用得当,它会促进更简单、更易于使用、更易于维护和更正确的自文档化代码。
我和你有同样的问题,我觉得“合同”的解释有点混乱。
如果您指定一个方法将 IEnumerable 接口作为参数,您可以说这是一个约定,指定参数必须是继承自 IEnumerable 接口的类型,因此支持 IEnumerable 接口中指定的所有方法。如果我们使用抽象类或普通类,情况也是如此。从这些类继承的任何对象都可以作为参数传入。在任何情况下,您都可以说继承的对象支持基类中的所有公共方法,无论基类是普通类、抽象类还是接口。
具有所有抽象方法的抽象类与接口基本相同,因此您可以说接口只是一个没有实现方法的类。您实际上可以从语言中删除接口,而只使用带有抽象方法的抽象类。我认为我们将它们分开的原因是出于语义原因,但出于编码原因,我没有看到原因并且发现它只是令人困惑。
另一个建议可能是将接口重命名为接口类,因为接口只是类的另一种变体。
在某些语言中存在细微的差异,允许一个类仅继承 1 个类但有多个接口,而在其他语言中,您可以同时拥有很多,但这是另一个问题,我认为没有直接关系
理解接口的最简单方法是从考虑类继承的含义开始。它包括两个方面:
这两个特性都很有用,但是因为很难让一个类使用多个类的成员作为自己的成员,所以许多语言和框架只允许类从单个基类继承。另一方面,让一个类可以替代多个其他不相关的东西并没有什么特别的困难。
此外,由于继承的第一个好处可以在很大程度上通过封装来实现,因此允许第一种类型的多重继承的相对好处是有限的。另一方面,能够用一个对象代替多种不相关类型的事物是一种有用的能力,如果没有语言支持,这种能力就无法轻易实现。
Interfaces provide a means by which a language/framework can allow programs to benefit from the second aspect of inheritance for multiple base types, without requiring it to also provide the first.
Interface is like a fully abstract class. That is, an abstract class with only abstract members. You can also implement multiple interfaces, it's like inheriting from multiple fully abstract classes. Anyway.. this explanation only helps if you understand what an abstract class is.
接口要求实现它们的任何类包含接口中定义的方法。
目的是,无需查看类中的代码,您就可以知道它是否可用于特定任务。比如Java中的Integer类实现了Comparable接口,所以,如果你只看到方法头(公共类String实现Comparable),你就知道它包含了一个compareTo()方法。
show()
在您的简单情况下,您可以通过使用实现(或可能将其定义为抽象)的公共基类来实现与接口所获得的类似的东西。让我将您的通用名称更改为更具体的名称,Eagle和Hawk而不是MyClass1和MyClass2。在这种情况下,您可以编写如下代码
Bird bird = GetMeAnInstanceOfABird(someCriteriaForSelectingASpecificKindOfBird);
bird.Fly(Direction.South, Speed.CruisingSpeed);
这使您可以编写可以处理任何东西的代码 Bird。然后,您可以编写代码,使Bird执行其操作(飞行、进食、产卵等),该操作作用于它视为Bird的实例。无论Bird是否真的是Eagle、Hawk或其他任何从Bird派生的东西,该代码都可以使用。
但是,当您没有真正的关系时,这种范式开始变得混乱。假设您想编写在天空中飞行的代码。如果您编写该代码以接受Bird基类,则突然很难将该代码演变为在JumboJet实例上工作,因为虽然Bird和JumboJet肯定都可以飞行,但JumboJet肯定不是Bird。
进入界面。
Bird (和Eagle和Hawk)的共同点是它们都能飞。如果您编写上面的代码而不是作用于接口IFly,则该代码可以应用于任何提供该接口实现的东西。