我为什么要使用接口?

我明白,他们强迫你实施方法等,但我不明白的是为什么你会想要使用它们。 有谁能给我一个很好的例子或解释为什么我想要实现这一点。

一个具体的例子:接口是指定其他人的代码必须符合的合同的好方法。

如果我正在编写一个代码库,则可以编写对具有一定行为集合的对象有效的代码。 最好的解决scheme是在接口中指定这些行为(没有实现,只是一个描述),然后在我的库代码中使用对实现该接口的对象的引用。

然后任何随机的人可以来,创build一个实现该接口的类,实例化该类的一个对象,并将其传递给我的库代码,并期望它的工作。 注意:当然可以严格实现一个接口,而忽略接口的意图,所以仅仅实现一个接口并不能保证事情能够正常工作。 愚蠢总是find一个方法! 🙂

另一个具体的例子是:两个小组合作不同组成部分,必须合作。 如果两个团队在第一天坐下来就一组接口达成一致意见,那么他们可以采取各自独立的方式,在这些接口周围实现他们的组件。 团队A可以构build模拟B队组件进行testing的testing工具,反之亦然。 并行开发,更less的错误。

关键是接口提供了一个抽象层,以便您可以编写对不必要的细节无知的代码。

大多数教科书中使用的典型例子是sorting例程。 你可以sorting任何类的对象,只要你有一个方法来比较任何两个对象。 您可以通过实现IComparable接口来使任何类可sorting,因此迫使您实现比较两个实例的方法。 所有的sorting例程都是为处理对IComparable对象的引用而编写的,因此,只要实现了IComparable,就可以在类的对象集合上使用这些sorting例程中的任何一个。

界面定义合同 ,这是关键词。

当你需要在你的程序中定义一个合同的时候,你使用了一个接口,但是只要真的这样,你并不真正关心该类的其他属性。

所以,我们来看一个例子。 假设你有一个提供sorting列表function的方法。 首先,什么是列表? 你真的关心什么元素,为了整理列表? 你的答案应该是no …在.NET中(例如)你有一个名为IList的接口,它定义了列表必须支持的操作,所以你不关心表面下的实际细节。

回到这个例子,你不是真的知道列表中的对象的类别…你不在乎。 如果你可以比较一下这个对象,那么你也可以对它们进行sorting。 所以你宣布合同:

 interface IComparable { // Return -1 if this is less than CompareWith // Return 0 if object are equal // Return 1 if CompareWith is less than this int Compare(object CompareWith); } 

该合同指定接受一个对象并返回一个int的方法必须被实现以便可比较。 现在你已经定义了一个合同,现在你不关心对象本身,而是关于合同,所以你可以这样做:

 IComparable comp1 = list.GetItem(i) as IComparable; if (comp1.Compare(list.GetItem(i+1)) < 0) swapItem(list,i, i+1) 

PS:我知道这些例子有点幼稚,但是它们是例子。

一个典型的例子是插件架构。 开发人员A编写主应用程序,并希望确保由开发人员B,C和D编写的所有插件都符合他的应用程序期望的内容。

一辆车上的踏板实现一个界面。 我来自美国,我们在路的右边开车。 我们的方向盘在车的左侧。 从左到右手动变速器的踏板是离合器 – >制动器 – >加速器。 当我去爱尔兰的时候,驾驶是颠倒的。 汽车的方向盘在右边,他们开车在路的左边…但踏板,啊踏板…他们实现了相同的界面…所有三个踏板都是在同一个顺序…所以即使class级不同,class级运营的networking也不一样,我还是习惯了踏板界面。 就像其他车一样,我的大脑能够把这辆车叫做我的肌肉。

想想我们无法生存的众多非编程接口。 然后回答你自己的问题。

理解接口最简单的方法是允许不同的对象暴露COMMONfunction。 这使得程序员可以编写更简单,更短的代码来编写接口,然后只要对象实现了接口就可以工作。

示例1:有许多不同的数据库提供者,MySQL,MSSQL,Oracle等。但是所有的数据库对象都可以做同样的事情,所以你会发现许多数据库对象的接口。 如果一个对象实现了IDBConnection,那么它暴露了方法Open()和Close()。 所以,如果我希望我的程序是数据库提供者不可知的,我编程接口而不是特定的提供者。

 IDbConnection connection = GetDatabaseConnectionFromConfig() connection.Open() // do stuff connection.Close() 

通过编程查看接口(IDbconnection)我现在可以在我的configuration中将任何数据提供程序交换,但我的代码保持完全一样。 这种灵活性非常有用,易于维护。 这样做的缺点是我只能执行“通用”的数据库操作,并不能充分利用每个特定提供商提供的优势,因为编程中的所有内容都有一个权衡,您必须确定哪种scheme最有利于您。

例2:如果你注意到几乎所有的集合都实现了这个名为IEnumerable的接口。 IEnumerable返回具有MoveNext(),Current和Reset()的IEnumerator。 这使得C#可以轻松移动您的集合。 它可以做到这一点的原因是因为它暴露IEnumerable接口,它知道对象公开了它需要通过它的方法。 这有两件事。 1)foreach循环现在将知道如何枚举集合,2)现在可以将强大的LINQexpression式应用于您的集合。 同样,接口在这里非常有用的原因是因为所有的集合都有COMMON中的东西,所以它们可以被移动。 每个集合都可以通过不同的方式移动(链接列表vs数组),但接口的美妙之处在于,实现对于接口的消费者来说是隐藏的和不相关的。 MoveNext()为您提供了集合中的下一个项目,它并不重要。 相当不错,对吧?

例3:当你devise自己的接口时,你只需要问自己一个问题。 这些东西有什么共同点? 一旦find所有对象共享的东西,就可以将这些属性/方法抽象成一个接口,以便每个对象都可以inheritance它。 然后你可以使用一个接口对几个对象进行编程。

当然,我必须给我最喜欢的C ++多态的例子,动物的例子。 所有动物都有一定的特征。 让我们说他们可以移动,说话,他们都有一个名字。 因为我刚刚确定了我的所有动物的共同之处,我可以将这些特征抽象为IAnimal接口。 然后我创build一个Bear对象,一个Owl对象和一个Snake对象来实现这个接口。 您可以将不同的对象存储在一起来实现相同的接口的原因是因为接口表示IS-A替代权。 一只熊是一只动物,一只猫头鹰是一只动物,所以它使我可以把它们全部作为动物收集起来。

 var animals = new IAnimal[] = {new Bear(), new Owl(), new Snake()} // here I can collect different objects in a single collection because they inherit from the same interface foreach (IAnimal animal in animals) { Console.WriteLine(animal.Name) animal.Speak() // a bear growls, a owl hoots, and a snake hisses animal.Move() // bear runs, owl flys, snake slithers } 

你可以看到,即使这些动物以不同的方式执行每个动作,我可以在一个统一的模型中对它们进行编程,这只是接口的许多好处之一。

所以接口最重要的是对象的共同点是什么,这样你就可以用同样的方式对不同的对象进行编程。 节省时间,创build更灵活的应用程序,隐藏复杂性/实现,模拟真实世界的对象/情况等诸多好处。

希望这可以帮助。

在面向对象的系统中,接口是绝对必要的,这个系统可以很好地利用多态性。

一个经典的例子可能是IVehicle,它有一个Move()方法。 你可以上课车,自行车和坦克,实施IVehicle。 他们都可以移动(),你可以写代码,不关心它处理什么样的车辆,只要它可以移动()。

 void MoveAVehicle(IVehicle vehicle) { vehicle.Move(); } 

接口是一种多态的forms。 一个例子:

假设你想写一些日志代码。 日志logging将会到达某处(可能是文件或主代码运行的设备上的串行端口,或套接字,或像/ dev / null一样被丢弃)。 你不知道在哪里:你的日志代码的用户需要自由确定。 事实上,你的日志代码并不在乎。 它只是想要可以写入字节的东西。

所以,你发明了一个叫做“你可以写字节的东西”的接口。 日志代码给出了这个接口的一个实例(可能在运行时,也许是在编译时configuration的,它仍然是多态,只是不同的types)。 您可以编写一个或多个实现接口的类,只需更改日志代码将使用哪一个,就可以轻松更改日志logging的位置。 其他人可以通过编写自己的接口实现来更改日志logging的位置,而无需更改代码。 基本上这就是多态的意义所在 – 只要知道一个对象就可以以特定的方式使用它,同时又能让它在所有你不需要知道的方面变化。 一个界面描述你需要知道的东西。

C的文件描述符基本上是一个“我可以读取和/或写入字节和/或”的接口,几乎每种types的语言都有其标准库中存在的接口:stream或其他。 非types化语言通常具有代表stream的非正式types(可能称为契约)。 所以在实践中,你几乎不必亲自发明这个特定的接口:使用语言给你的东西。

日志logging和数据stream只是一个例子 – 只要你能抽象地描述一个对象应该做什么,但是不想把它绑定到一个特定的实现/类/任何东西,接口就会发生。

 When you need different classes to share same methods you use Interfaces. 

有很多理由这样做。 当你使用一个接口的时候,你需要重构/重写代码,以后就可以做好准备了。 您还可以为简单的操作提供一种标准化的API。

例如,如果你想编写一个像quicksort这样的sortingalgorithm,你只需要sorting任何对象列表,就可以成功地比较两个对象。 如果你创build一个接口,比如说ISortable,任何创build对象的人都可以实现ISortable接口,他们可以使用你的sorting代码。

如果您正在编写使用数据库存储的代码,并且您正在写入存储界面,则可以将该代码replace为该行。

接口鼓励你的代码松散耦合,这样你可以有更大的灵活性。

想象一下定义一个基本的CRUD机制的以下基本接口:

 interface Storable { function create($data); function read($id); function update($data, $id); function delete($id); } 

从这个接口中,可以看出实现它的任何对象都必须具有创build,读取,更新和删除数据的function。 这可以通过数据库连接,CSV文件阅读器和XML文件阅读器,或任何其他types的可能想要使用CRUD操作的机制。

因此,你现在可以有如下的东西:

 class Logger { Storable storage; function Logger(Storable storage) { this.storage = storage; } function writeLogEntry() { this.storage.create("I am a log entry"); } } 

这个logging器不关心你是否通过数据库连接,或者操纵磁盘上的文件。 所有它需要知道的是,它可以调用它的create(),它会像预期的那样工作。

接下来的问题是,如果数据库和CSV文件等都可以存储数据,不应该从一个普通的Storable对象inheritance,因此不需要接口? 答案是否定的…不是每个数据库连接都可能执行CRUD操作,同样适用于每个文件读取器。

接口定义了对象能够做什么以及如何使用它…不是它是什么!

正如你所指出的,当你想强迫某人以某种格式创build界面时,界面非常有用。

如果数据不是以某种格式存在,那么接口是很好的,这意味着在代码中做出危险的假设。

例如,目前我正在编写一个将数据从一种格式转换为另一种格式的应用程序。 我想强迫他们把这些领域放在一边,所以我知道他们会存在,并有更大的机会得到正确的实施。 我不在乎是否有另一个版本出来,它不能编译它们,因为反正这个数据更可能是需要的。

因为这个原因,接口很less被使用,因为通常你可以做出假设或者不需要数据去做你需要做的事情。

一个接口,仅仅定义了接口 。 稍后,您可以定义将接口作为参数(或更准确地说,实现该接口的对象)的方法(在其他类上)。 这样,你的方法就可以在各种各样的对象上运行,唯一的共同点是它们实现了这个接口。

首先,他们给你一个额外的抽象层。 你可以说“对于这个函数,这个参数必须是具有这些参数的这些方法的对象”。 而且你可能也想设置这些方法的含义,以某种方式抽象出来,但是允许你对代码进行推理。 在鸭式语言中,你可以免费获得。 不需要明确的语法“接口”。 然而,你可能仍然创build了一系列的概念界面,比如契约devise(Contract by Contract)。

此外,接口有时用于“纯”目的。 在Java中,它们可以用来模拟多重inheritance。 在C ++中,可以使用它们来减less编译时间。

一般来说,它们会减less代码中的耦合。 这是好事。

您的代码也可能更容易以这种方式进行testing。

假设你想跟踪一堆东西。 所述集合必须支持一些东西,比如添加和删除项目,并检查项目是否在集合中。

然后你可以使用方法add(),remove()和contains()来指定一个接口ICollection。

不需要知道什么样的集合(List,Array,Hash-Table,Red-black树等)的代码可以接受实现该接口的对象,并在不知道它们的实际types的情况下使用它们。

在.Net中,我创build基类,并在类相关时从它们inheritance。 例如,基类Person可以被Employee和Customerinheritance。 人可能有共同的属性,如地址栏,名称,电话等等。 员工可能拥有自己的部门财产。 客户拥有其他专有财产。

由于类只能从.Net中的另一个类inheritance,因此我使用接口来实现其他共享function。 有时接口是由不相关的类共享的。 使用接口创build一个开发人员将会知道的合同,由所有实现它的其他类共享。 我也强迫这些class级执行其所有成员。

在我的博客文章中,我简要介绍了接口的三个目的。

接口可能有不同的用途:

  • 为相同的目标提供不同的实现。 典型的例子是一个列表,它可能对不同的性能用例(LinkedList,ArrayList等)有不同的实现。
  • 允许标准修改。 例如,一个sorting函数可以接受一个Comparable接口,以便根据相同的algorithm提供任何种类的sorting标准。
  • 隐藏实施细节。 这也使得用户更容易阅读评论,因为在界面的主体中只有方法,字段和评论,没有长的代码块跳过。

以下是文章的全文: http : //weblogs.manas.com.ar/ary/2007/11/

在C#中,接口对于为不共享相同基类的类提供多态性也非常有用。 意思是,因为我们不能有多重inheritance,所以可以使用接口来允许使用不同的types。 这也是一种允许您公开私有成员而无需reflection(显式实现)的方式,所以它可以是在保持对象模型清洁的同时实现function的一种好方法。

例如:

 public interface IExample { void Foo(); } public class Example : IExample { // explicit implementation syntax void IExample.Foo() { ... } } /* Usage */ Example e = new Example(); e.Foo(); // error, Foo does not exist ((IExample)e).Foo(); // success 

我认为你需要对devise模式有一个很好的理解,以便看到有力量。

检查首先devise模式

在这里还有几个类似的问题的答案: 接口与基类

我见过的最好的Java代码几乎把所有的对象引用定义为接口的实例,而不是类的实例。 这是为灵活性和变化而devise的高质量代码的强烈信号。